Java 中 String 的比较方式(== 和 equals)

基本概念

在 Java 中,String 既可以作为一个对象来使用,又可以作为一个基本类型来使用 这里指的作为一个基本类型来使用只是指使用方法上的,比如 String s = "hello",它的使用方法如同基本类型 int 一样,比如 int i = 1;,而作为一个对象来使用,则是指通过 new 关键字来创建一个新对象,比如 String s = new String("Hello")

Java 中 String 比较的方法有两种: 1)用 "==" 来比较。这种比较是比较两个 String 类型变量的引用是否相同 (即是否指相同的内存地址) 2)用 Object 对象的 equals() 方法来比较。String 对象继承自 Object,并且对 equals () 方法进行了重写。两个 String 对象通过 equals () 方法来进行比较时,也就是对 String 对象的实际内容进行比较。

实际例子

String 作为对象时的比较

1
2
3
4
5
6
7
8
9
String s1 = new String("Hello");
String s2 = new String("Hello");

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

/*output*/
false
true

两个 String 对象都是通过 new 创建出来的,而 new 关键字为创建的每个对象分配一块新的、独立的内存堆,因此当通过 "==" 来比较它们的引用是否相同时,将返回 false;而通过 equals() 方法来比较时,则返回 true,因为这两个对象所封装的字符串内容是完全相同的。

String 作为基本类型时的比较

1
2
3
4
5
6
7
8
9
10
String s1 = "Hello";
String s2 = "Hello";

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

/*output*/
true
true

由于这两个 String 对象都是作为一个基本类型来使用的,而不是通过 new 关键字来创建的,因此虚拟机不会为这两个 String 对象分配新的内存堆,而是到 String 缓冲池中来寻找。

什么是 String 缓冲池?在 Java 中,由于 String(final)是不可改变的,为了提高效率,不重复创建新的字符创,Java 引用了 String 缓冲池的概念。

首先为 s1 寻找 String 缓冲池内是否有与 "Hello" 相同值的 String 对象存在,此时 String 缓冲没有相同值的 String 对象存在,所以虚拟机会在 String 缓冲池内创建此 String 对象,其动作就是 new String ("Hello");。然后把此 String 对象的引用赋值给 s1

接着为 s2 寻找 String 缓冲池内是否有与 "Hello" 相同值的 String 对象存在,此时虚拟机找到了一个与其相同值的 String 对象,这个 String 对象其实就是为 s1 所创建的 String 对象。既然找到了一个相同值的对象,那么虚拟机就不在为此创建一个新的 String 对象,而是直接把存在的 String 对象的引用赋值给 s2。

这里既然 s1 和 s2 所引用的是同一个 String 对象,即自己等于自己,所以以上两种比较方法都返回 ture。 。

对象与基本类型的比较

1
2
3
4
5
6
7
8
9
10
String s1 = "Hello";
String s2 = new String("Hello");

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

/*output*/
false
true

由于new 关键字会申请新的内存空间,创建新的对象,因此不会去查找缓存池,即使缓存池中有 "Hello",因此两者的内存地址不是一样的,所以第一个输出为 false,而两者的内容是一样的,输出为 true。

将上面的代码稍作修改

1
2
3
4
5
6
7
8
9
10
11
String s1 = "Hello";
String s2 = new String("Hello");
s2 = s2.intern();

System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

/*output*/
true
true

上面的代码增加了一行 s2 = s2.intern();其作用是从 String 缓冲池内取出一个与其值相同的 String 对象的引用赋值给 s(假如有的话)

这样做的原因是如果频繁地创建相同内容的对象,虚拟机分配许多新的内存堆,虽然它们的内容是完全相同的。由于 String 是 final 类,因此 String 对象在创建后不能改变。所以为了节省内存,可以使用 String 缓冲池,因为 String 缓冲池内不会存在相同内容的 String 对象。而 intern() 方法就是使用这种机制的途径。

在一个已实例化的 String 对象 s 上调用 intern() 方法后,虚拟机会在 String 缓冲池内寻找与此 s 对象存储内容相同的 String 对象,如果能找到,则返回对象在缓冲池中的地址,如果找不到,那么虚拟机在缓冲池中以 s 的内容新建一个对象并返回这个对象的地址。注意需要将 s 指向返回的缓冲池对象的地址,这样才能通过垃圾回收器回将原先那个通过 new 关键字所创建出的 String 对象回收。

因此可以解释上面的 s1==s2 返回结果为什么是 true 了,因为此时,两者的引用相同,均指向缓冲池的对象。

拼接字符串后的比较

上面提到由于 String 是 final 类,因此 String 对象在创建后不能改变,那么像拼接字符串的操作如 String s1=s2+s3;(s2、s3 是已赋值的 String)应该会产生一个新的对象,用新的地址空间存储。但是这句话也不完全对,详见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
String s1 = "a";  
String s2 = "b";
String s3 = "ab";
String s4 = "a"+"b";
System.out.println("s3==s4? "+ (s3==s4));

String s5 = s1+s2;
System.out.println("s3==s5? "+ (s3==s5));

final String s6 = "a" ;
final String s7 = "b" ;
String s8 = s6 + s7;
System.out.println("s3==s8? "+ (s3==s8));

输出如下:

1
2
3
s3==s4? true
s3==s5? false
s3==s8? true

s4 由 "a"、"b" 两个常量拼接而成,本来按照上面的说法应该会生成新的内存空间,但是因为 "a"、"b" 为两个为常量,不可变,在编译期间编译器会把 s5="a"+"b" 优化为 s5="ab"。

s5 由 s1 和 s2 拼接而成,由于两个变量的相加所以编译器无法优化, 在运行时,会有新的 String 地址空间的分配,而不是指向缓冲池中的 “ab”。所以结果 false。

s6 虽让也是由两个变量拼接而成,但是这两个变量已经声明为 final 不可变的了,所以类似于 s4,在编译期间编译器也进行了优化确定了 s8 的值。


参考: http://blog.csdn.net/wangdong20/article/details/8566217 http://www.itxxz.com/a/tea/2014/0814/208.html http://renxiangzyq.iteye.com/blog/549554