首先,先解释一下几个关键字:
1、常量池
常量池可以理解为在堆内存中开辟的一块空间,它只适用于 String 类,“常量池”的出现是为了方便取用某些需要重复引用的东西,在使用前可以先在常量池中查找,如果没有则创建一个,同时也避免了资源浪费
2、字面量
可以理解为变量的值,例如:
int a = 10; //10 即为字面量
String str = "abc"; //abc 为字面量
下面我们说一下比较字符串值的几种情况
1、直接赋值
- 以 “” 方式创建的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象存储在常量池中(请参考下面的 字符串共享),并在字符串池中维护
String str = "abc";
String str1 = "abc";
System.out.println(str == str1); //结果为 true
- 创建对象
str
:
我们在创建str
的时候,因为在此之前没有声明过该对象,那么在常量池中也就不会有对应的字面量abc
,这个时候就会在常量池中开辟一块空间存储这个字面量abc
; - 创建对象
sr1
:
因为我们之前已经创建了str
,而str
的值和str1
的字面量abc
又是一样的,所以在创建str1
对象的时候它就也指向了常量池中的字面量abc
,这是就触发了一个新的概念(字符串共享)
字符串共享:
- 当创建一个对象时,如果该对象的字面量和常量池中存在字面量一致,那么它就不再创建一个新的字面量,而是也指向之前的字面量,也就意味着两个字符串对象所指向的地址值是相同的,所以
str == str1
,该种情况可以避免重复对象的产生
第二种直接赋值的情况:
String str = "1" + "2" + "3";
String str1 = "1" + "23";
String str2 = "123";
-
针对以上三种赋值的方法,事实上它们的值在代码底层都会解析为
“123”
,该种形式叫做编译优化,我们在执行 .java 文件的时候,通过会生成一个编译后的文件,也就是以 .class 后缀结尾的文件,以上面的例子来讲,该代码在class文件编译后的结果如下 -
所以可以看出,代码中的三句输出的结果一定都为
true
2、通过构造函数创建字符串对象
- 每次使用new创建对象时,都会在堆内存中申请新空间来存储字符串对象,所以,通过构造方法创建的每一个字符串对象都是单独存储在堆内存中的,不存在字符串的共享
例如:
String str1 = "abc"; //直接赋值
String str2 = new String("abc"); //通过构造函数创建
System.out.println(str1 == str2); //结果为 False
-
当该语句执行之后,main 方法会进行压栈,此时,创建的对象会在堆内的常量池中查询是否已经存在
abc
,如果不存在,则在字符串常量池中创建该字符串,并且在堆内开辟一个空间,将堆内存中的地址值赋值给str1
.如果常量池中已经存在该字符串,则直接在堆内存内开辟空间并将地址值赋给str1
-
等于说创建了两个对象,一个字符串对象,存储在堆内存中,另一个为 abc,存储在常量池中
-
此时将
str1
和str2
的值进行比较,他对比的是两个对象的内存地址,str1
指向的是常量池中的abc
,而str2
指向的是堆内存中的new string
,内存地址不同,所以结果为 False