吴良超的学习笔记

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
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
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
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