# java基础面试1

返回:java面试

# JDK和JRE有什么区别

back

JDK是用于开发的而JRE是用于运行Java程序的。
JDK和JRE都包含了JVM,从而使得我们可以运行Java程序。
JVM是Java编程语言的核心并且具有平台独立性

# ==和equals的区别是什么

equals()“==”操作用于对象的比较,检查俩对象的相等性,但是他们俩的主要区别在于前者是方法后者是操作符
由于java不支持操作符重载(overloading),“==”的行为对于每个对象来说与equals()是完全相同的,但是equals()可以基于业务规则的不同而重写(overridden )。
另一个需要注意的不同点是“==”习惯用于原生(primitive)类型之间的比较,而equals()仅用于对象之间的比较。
“==”或等号操作在Java编程语言中是一个二元操作符,用于比较原生类型和对象。就原生类型如boolean、int、float来说,使用“==”来比较两者,这个很好掌握。但是在比较对象的时候,就会与equals()造成困惑。“==”对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时,“==”操作将返回true,否则返回false。
equals()方法定义在Object类里面,根据具体的业务逻辑来定义该方法,用于检查两个对象的相等性。例如:两个Employees被认为是相等的如果他们有相同的empId的话,你可以在你自己的domain对象中重写equals方法用于比较哪两个对象相等。equals与hashcode是有契约的(无论什么时候你重写了equals方法,你同样要重写hashcode()方法),默认的equals方法实现是与“==”操作一样的,基于业务需求重写equals方法是最好的实践之一,同样equals与compareTo保持一致也不足为奇,以至于存储对象在Treemap或treeset集合中时,将使用compareTo方法检查相等性,行为是一致的。
==与equals的主要区别是:==常用于比较原生类型,而equals()方法用于检查对象的相等性。另一个不同的点是:如果==和equals()用于比较对象,当两个引用地址相同,==返回true。而equals()可以返回true或者false主要取决于重写实现。最常见的一个例子,字符串的比较,不同情况==和equals()返回不同的结果。

# 字符串的==和equals对比

字符串的比较是一个常见的情景,因为java.lang.String类重写了equals方法,它返回true如果两个字符串对象包含有相同的内容,但是==只有他们的引用地址相同时才返回true,下面这个例子就是通过==和equals方法分别比较两个字符串。

String personalLoan = new String("cheap personal loans");
    String homeLoan = new String("cheap personal loans");

    //since two strings are different object result should be false
    boolean result = personalLoan == homeLoan;
System.out.println("Comparing two strings with == operator: " + result);

//since strings contains same content , equals() should return true
    result = personalLoan.equals(homeLoan);
System.out.println("Comparing two Strings with same content using equals method: " + result);

    homeLoan = personalLoan;
//since both homeLoan and personalLoand reference variable are pointing to same object
//"==" should return true
    result = (personalLoan == homeLoan);
System.out.println("Comparing two reference pointing to same String with == operator: " + result);

//    Output:
//    Comparing two strings with == operator: false
//    Comparing two Strings with same content using equals method: true
//    Comparing two reference pointing to same String with == operator: true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# ==与equals在对象之间的对比

另一中情景是:当你对比两个对象时,在选择==和equals方法中很容易困惑,当你比较的两个引用指向的对象是Object的类型的时候,那么你看到的结果和==是一致的,因为默认的equals方法实现仅仅比较的内存地址。如果两个引用变量完全指向的是同一个对象的话就返回true,下面这个例子是equals和==方法操作比较的是两个对象。

Object obj1 = new Object();
    Object obj2 = new Object();

// == should return false
    result = (obj1==obj2);
System.out.println("Comparing two different Objects with == operator: " + result);

//equals should return false because obj1 and obj2 are different
    result = obj1.equals(obj2);
System.out.println("Comparing two different Objects with equals() method: " + result);

// "==" should return true because both obj1 and obj2 points same object
    obj1=obj2;
    result = (obj1==obj2);
System.out.println("Comparing two reference pointing to same Object with == operator: " + result);

//    Output:
//    Comparing two different Objects with == operator: false
//    Comparing two different Objects with equals() method: false
//    Comparing two reference pointing to same Object with == operator: true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 总结

使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。
==返回true如果两个引用指向相同的对象,equals()的返回结果依赖于具体业务实现,字符串的对比使用equals()代替==操作符
以上就是关于equals方法和==操作符的区别,其主要的不同是一个是操作符一个是方法==用于对比原生类型而equals()方法比较对象的相等性

# 两个对象的hashCode()相同则equals()也一定为true对吗

back

# equals()

java.lang.Object类中有两个非常重要的方法:

public boolean equals(Object obj)
public int hashCode()
1
2

Object类是类继承结构的基础,所以是每一个类的父类。所有的对象,包括数组,都实现了在Object类中定义的方法。 equals()方法是用来判断其他的对象是否和该对象相等.
equals()方法在object类中定义如下:

public boolean equals(Object obj) {
    return (this == obj);
}
1
2
3

很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们知道,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。
比如在String类中如下:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n– != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

很明显,这是进行的内容比较,而已经不再是地址的比较。依次类推Math、Integer、Double等这些类都是重写了equals()方法的,从而进行的是内容的比较。当然,基本类型是进行值的比较。
它的性质有

  • 自反性(reflexive)。对于任意不为null的引用值x,x.equals(x)一定是true。
  • 对称性(symmetric)。对于任意不为null的引用值x和y,当且仅当x.equals(y)是true时,y.equals(x)也是true。
  • 传递性(transitive)。对于任意不为null的引用值x、y和z,如果x.equals(y)是true,同时y.equals(z)是true,那么x.equals(z)一定是true。
  • 一致性(consistent)。对于任意不为null的引用值x和y,如果用于equals比较的对象信息没有被修改的话,多次调用时x.equals(y)要么一致地返回true要么一致地返回false。

对于任意不为null的引用值x,x.equals(null)返回false。
对于Object类来说,equals()方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null的引用值x和y,当且仅当x和y引用的是同一个对象,该方法才会返回true。 需要注意的是当equals()方法被override时,hashCode()也要被override。按照一般hashCode()方法的实现来说,相等的对象,它们的hash code一定相等。

# hashCode()

hashCode()方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。
它的性质是:

  • 在一个Java应用的执行期间,如果一个对象提供给equals做比较的信息没有被修改的话,该对象多次调用hashCode()方法,该方法必须始终如一返回同一个integer。
  • 如果两个对象根据equals(Object)方法是相等的,那么调用二者各自的hashCode()方法必须产生同一个integer结果。并不要求根据equals(java.lang.Object)方法不相等的两个对象,调用二者各自的hashCode()方法必须产生不同的integer结果。然而,程序员应该意识到对于不同的对象产生不同的integer结果,有可能会提高hash table的性能。

大量的实践表明,由Object类定义的hashCode()方法对于不同的对象返回不同的integer。
在object类中,hashCode定义如下:

public native int hashCode();
1

说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double等这些类都是覆盖了hashcode()方法的。
例如在String类中定义的hashcode()方法如下:

public int hashCode() {
    int h = hash;
    if (h == 0) {
        int off = offset;
        char val[] = value;
        int len = count;

        for (int i = 0; i < len; i++) {
            h = 31 * h + val[off++];
        }
        hash = h;
    }
    return h;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

解释一下这个程序(String的API中写到):s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂(空字符串的哈希码为 0)。想要弄明白hashCode的作用,必须要先知道Java中的集合。
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

# 简而言之

在集合查找时,hashcode能大大降低对象比较次数,提高查找效率!Java对象的eqauls方法和hashCode方法是这样规定的:

  • 1、相等(相同)的对象必须具有相等的哈希码(或者散列码)。
  • 2、如果两个对象的hashCode相同,它们并不一定相同。

以下是Object对象API关于equal方法和hashCode方法的说明:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result. It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
关于第一点,相等(相同)的对象必须具有相等的哈希码(或者散列码),为什么?
想象一下,假如两个Java对象A和B,A和B相等(eqauls结果为true),但A和B的哈希码不同,则A和B存入HashMap时的哈希码计算得到的HashMap内部数组位置索引可能不同,那么A和B很有可能允许同时存入HashMap,显然相等/相同的元素是不允许同时存入HashMap,HashMap不允许存放重复元素。
关于第二点,两个对象的hashCode相同,它们并不一定相同,也就是说,不同对象的hashCode可能相同;假如两个Java对象A和B,A和B不相等(eqauls结果为false),但A和B的哈希码相等,将A和B都存入HashMap时会发生哈希冲突,也就是A和B存放在HashMap内部数组的位置索引相同这时HashMap会在该位置建立一个链接表,将A和B串起来放在该位置,显然,该情况不违反HashMap的使用原则,是允许的。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。

# hashCode()小结

  • 1.如果两个对象相同,那么它们的hashCode值一定要相同;
  • 2.如果两个对象的hashCode相同,它们并不一定相同(这里说的对象相同指的是用eqauls方法比较)。 如不按要求去做了,会发现相同的对象可以出现在Set集合中,同时,增加新元素的效率会大大下降。
  • 3.equals()相等的两个对象,hashcode()一定相等;equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。

换句话说,equals()方法不相等的两个对象,hashcode()有可能相等(我的理解是由于哈希码在生成的时候产生冲突造成的)。反过来,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

# final在java中有什么作用

back

Java中的final关键字非常重要,它可以应用于类、方法以及变量。这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使用final关键字的实例。final经常和static一起使用来声明常量,你也会看到final是如何改善应用性能的。
final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。 凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。
final变量是只读的。
final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
下面是final方法的例子:

class PersonalLoan{
    public final String getName(){
        return "personal loan";
    }
}

class CheapPersonalLoan extends PersonalLoan{
    @Override
    public final String getName(){
        return "cheap personal loan"; //compilation error: overridden method is final
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。Java中有许多类是final的,譬如String, Interger以及其他包装类。
下面是final类的实例:

final class PersonalLoan{
}
class CheapPersonalLoan extends PersonalLoan{  //compilation error: cannot inherit from final class
}
1
2
3
4

# final关键字的好处

下面总结了一些使用final关键字的好处:

  • final关键字提高了性能。JVM和Java应用都会缓存final变量。
  • final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。

使用final关键字,JVM会对方法、变量及类进行优化。

# 不可变类

创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。

# 关于final的重要知识点

  • final关键字可以用于成员变量、本地变量、方法以及类。
  • final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  • 你不能够对final变量再次赋值。
  • 本地变量必须在声明时赋值。
  • 在匿名类中所有变量都必须是final变量。
  • final方法不能被重写。
  • final类不能被继承。
  • final关键字不同于finally关键字,后者用于异常处理。
  • final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
  • 接口中声明的所有变量本身是final的。
  • final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  • final方法在编译阶段绑定,称为静态绑定(static binding)。
  • 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
  • 按照Java代码惯例,final变量就是常量,而且通常常量名要大写:对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:
private final List Loans = new ArrayList();
list.add(“home loan”);  //valid
list.add("personal loan"); //valid
    loans = new Vector();  //not valid
1
2
3
4

# java中的Math.round(-1.5)等于多少

back

Math.round(-1.5)
-1
Math.round(1.5)
2
1
2
3
4

Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应。
例如,
ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;
floor的英文意义是地板,该方法就表示向下取整,Math.ceil(11.6)的结果为11,Math.ceil(-11.6)的结果是-12;
最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。

# String属于基础的数据类型吗

Java的8大基本数据类型分别是:

  • 整数类 byte, short, int, long
  • 浮点类 double, float
  • 逻辑类 boolean
  • 文本类 char
  • 此外需要说明的是,有的文章中把void也算是一种基本的数据类型。

基本类型的优势:数据存储相对简单,运算效率比较高。
包装类的优势:自带方法丰富,集合的元素必须是对象类型,体现了Java一切皆是对象的思想。

# java中操作字符串都有哪些类?它们之间有什么区别

back

# String

String是一个数据类型,但她并不是基本数据类型,而是一个被final修饰的、不可被继承的类,位于java.lang包。至于如何使用 String 类型,有两种方法,一是直接赋值,二是用new创建。
列举几个常见方法:

/* 返回一个新字符串,这个字符串将删除元字符串头部和尾部的空格 */
String trim()
/* 返回一个新字符串,这个字符串包含原始字符串中从 beginIndex 到串尾或 endIndex-1 位置的所以代码单元 */
String substring(int beginIndex)
String substring(int beginIndex, int endIndex)
1
2
3
4
5

# StringBuffer

了解了 String 类之后,我们会发现她有些缺陷,例如当我们创建了一个 String 类的对象之后,我们很难对她进行增、删、改的操作,为了解决这个弊端,
Java 语言就引入了 StringBuffer 类。StringBuffer 和 String 类似,只是由于 StringBuffer 的内部实现方式和 String 不同,StringBuffer 在进行字符串处理时,不用生成新的对象,所以在内存的使用上 StringBuffer 要优于 String 类。 在 StringBuffer 类中存在很多和 String 类一样的方法,这些方法在功能上和 String 类中的功能是完全一样的。但是有一个非常显著的区别在于,StringBuffer 对象每次修改都是修改对象本身,这点是其和 String 类的最大区别
此外,StringBuffer 是线程安全的,可用于多线程。而且 StringBuffer 对象的初始化与 String 对象的初始化不大一样,通常情况下,我们使用构造方法进行初始化,即:

// 声明一个空的 StringBuffer 对象
StringBuffer sb = new StringBuffer();

// 声明并初始化 StringBuffer 对象
StringBuffer sb = new StringBuffer("维C果糖");

// 下面的赋值语句是错的,因为 StringBuffer 和 String 是不同的类型
StringBuffer sb = "维C果糖";

// 下面的赋值语句也是错的,因为 StringBuffer 和 String 没有继承关系
StringBuffer sb = (StringBuffer)"维C果糖";

// 将 StringBuffer 对象转化为 String 对象
StringBuffer sb = new StringBuffer("维C果糖");
String str = sb.toString();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

接下来,介绍一些 StringBuffer 常用的 API 方法:

/* 构造一个空的字符串构建器 */
StringBuffer()

/* 返回构建器或缓冲器中的代码单元(字符)数量 */
int length()

/* 追加一个字符串并返回一个 this */
StringBuffer append(String str)

/* 追加一个字符并返回一个 this */
StringBuffer append(Char c)

/* 将第 i 个代码单元设置为 c */
void setCharAt(int i, char c)

/* 将构建器的内容进行顺序的反转 c */
StringBuffer reverse()

/* 返回一个与构建器或缓冲器内容相同的字符串 */
String toString()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# StringBuilder

在 JDK 5.0 之后,Java 语言又引入了 StringBuilder 类,这个类的前身是 StringBuffer,其效率略微有些低,但允许采用多线程的方式执行添加或者删除字符的操作。如果所有的字符串在一个单线程中(通常都是这样)编辑,则应该用 StringBuilder 代替她,这两个类的 API 是完全相同的。

# 字符串总结

  • 对于操作效率而言,一般来说,StringBuilder > StringBuffer > String;
  • 对于线程安全而言,StringBuffer 是线程安全的,可用于多线程;而 StringBuilder 是非线程安全的,用于单线程;
  • 对于频繁的字符串操作而言,无论是 StringBuffer 还是 StringBuilder,都优于 String。

此外,通过 String 创建的对象的是不可变的,而通过 StringBuffer 和 StringBuilder 创建的对象是可以变的,这里的变与不变,指的是字符串的内容和长度。

# String str="i"与 String str=new String(“i”)一样吗

在Java中,我们是如何创建一个类的实例的?
在我们常用的创建一个类的实例(对象)的方法有以下两种:

  • 一、使用new创建对象。
  • 二、调用Class类的newInstance方法,利用反射机制创建对象。
String s1 = "yveshe";
    String s2 = "yveshe";

System.out.println(s1 == s2);
System.out.println(s1 == "yveshe");
System.out.println("Hello Yves" == "Hello Yves");
1
2
3
4
5
6

通过前提介绍我们知道创建已给类的实例(对象)有两种方法:
通过new关键字创建对象,或者使用反射机制创建类的实例对象.显然在String s1 = "yveshe";产生String类的实例不包括在其中,这就是一种特的产生String类实例对象的方式.那么它的规则到底是怎样的呢?
String s1 = "yveshe";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"yveshe"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,该放在了字符串池中,再将它的引用返回。
在执行String s2 = "yveshe";其实字符串"yveshe"已经在常量池中存在,所以并不会创建新的对象而是将原来字面量"yveshe"的引用地址返回了。

# string总结

使用""创建的String对象的方式,首先会在常量池中查找是否已经存在字符串字面量,存在返回已经存在字符串的引用,否则先创建这个对象,该对象存放在String Pool中,然后将他的引用返回。所以这种创建了1(0)个对象。

String s= new String("YvesHe");
1

创建了几个对象,答案是2(1)个。
答案是创建了2个对象,一个是字面量"YvesHe"创建的String对象,在String Pool中,一个是new操作产生的String对象,存放在Java Heap中. (这里假定了StringPool中在代码运行之前不存在"YvesHe",否则则只会创建一个由于new操作产生的String对象,原理分析可以看第一大点) 我们可以把上面这行代码分成String s="YvesHe"new String()四部分来看待。 String s只是定义了一个名为sString类型的变量,因此它并没有创建对象; =是对变量s进行初始化,将某个对象的引用(或者叫句柄)赋值给它,显然也没有创建对象;
最后的new String(“YvesHe”)又能被看成" YvesHe "new String(); new 关键字一定会创建一个新的对象,存放在Java Heap中.

# +创建字符串

String s1 = "yves" + "he";
System.out.println(s1 == "yveshe");
1
2

输出true

反编译代码如下所示:

String s1 = "yveshe";
System.out.println(s1 == "yveshe");
1
2

String s1 = "yves" + "he";在编译期常量折叠成String s1 = "yveshe";所以输出结果为true.

String s1 = "yves";
String s2 = s1 + "he";
System.out.println(s2 == "yveshe");
1
2
3

输出为false

反编译代码:

String s1 = "yves";
String s2 = (new StringBuilder(String.valueOf(s1))).append("he").toString();
System.out.println(s2 == "yveshe");
1
2
3

从反编译的代码结果,我们可以知道s2的String实例对象的产生形式是使用的StringBuilder的toString方法底层是使用String的public String(char value[], int offset, int count)构造方法,这里产生的对象并不会放在String Pool中.
所以上面输出结果为false.如果打印语句修改为System.out.println(s2.intern() == "yveshe");,则会打印true.

# 总结intern()

intern()方法有两个作用,第一个是将字符串字面量放入常量池(如果池没有的话),第二个是返回这个常量的引用。

# 如何将字符串反转

back

new StringBuffer("huting").reverse().toString()
1

# 1、使用数组

将字符串转换为char数组,遍历循环给char数组赋值

public static String strReverseWithArray(String string){

    if(string==null||string.length()==0)return string;

    int length = string.length();

    char [] array = string.toCharArray();

    for(int i = 0;i<length;i++){

        array[i] = string.charAt(length-1-i);

    }

    return new String(array);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 优化:
    分析上一种解法,循环遍历时,我们不需要循环这么多次。每次循环的时候,我们应该直接给前、后位置都赋值。
public static String strReverseWithArray2(String string){

    if(string==null||string.length()==0)return string;

    int length = string.length();

    char [] array = string.toCharArray();

    for(int i = 0;i<length/2;i++){

        array[i] = string.charAt(length-1-i);

        array[length-1-i] = string.charAt(i);

    }

    return new String(array);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 2、使用栈

我们都知道,栈有"后进先出(LIFO)"的特点。这一特点刚好用于反转字符串。

  • 将字符串转换为char数组
  • 将char数组中的字符依次压入栈中
  • 将栈中的字符依次弹出赋值给char数组
public static String strReverseWithStack(String string){
    if(string==null||string.length()==0)return string;
    Stack<Character> stringStack = new Stack<>();
    char [] array = string.toCharArray();
    for(Character c:array){
        stringStack.push(c);
    }
    int length = string.length();
    for(int i= 0;i<length;i++){
        array[i] = stringStack.pop();
    }
    return new String(array);
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 3、逆序遍历

逆序遍历字符串中的字符,并将它依次添加到StringBuilder中

public static String strReverseWithReverseLoop(String string){
    if(string==null||string.length()==0)return string;
    StringBuilder sb = new StringBuilder();
    for(int i = string.length()-1;i>=0;i--){
        sb.append(string.charAt(i));
    }
    return sb.toString();
}
1
2
3
4
5
6
7
8

# 4、使用位运算

计算机的数据流本质上都是0,1二进制数据。字符串也是一样。而二进制数据的处理往往是通过位运算来实现的。位操作有:与,或,非,异或。
对位运算有过了解的应该知道,使用异或操作能实现交换两个变量的值而不引入第三个变量。

# 实现原理

首先介绍异或操作 异或操作: 当两两数值相同为否,而数值不同为真。写作A^B
A B A^B
0 0 0
0 1 1
1 0 1
1 1 0
使用异或操作交换数值
两个数异或的结果再与其中一个数异或的结果是另外一个数 这涉及到了离散数学中的异或的性质:

  • 1.交换律:A^B=B^A
  • 2.结合律: A^(B^C)=(A^B)^C
  • 3.恒等律:X^0=0
  • 4.归零律:X^X=0
  • 5.自反:A^B^B = A^0=A

# 5.使用递归

当我们反转字符串的时候,脑海里想的就是从首尾两端依次交换直到到达中间位置。当我们在反转某个字符时,剩下的字符串也是一个反转字符串的过程。这样,我们就能用递归的方法来实现反转字符串的目的。

具体思路是:
找出递归结束的临界条件
对针对于临界条件的不同的值做出不同的处理

public static String strReverseWithRecursive(String string){
    if(string==null||string.length()==0)return string;
    int length = string.length();
    if(length==1){
        return string;
    }else{
        return  strReverseWithRecursive(string.substring(1))+string.charAt(0);
    }
}
1
2
3
4
5
6
7
8
9