要了解这个问题,首先要搞清楚 jvm 的内存结构:
然后要搞清楚字符串常量池和运行时常量池在哪里,我们可以看到,运行时常量池在方法区中,而字符串常量池在堆中,需要注意的是,从 Java 7 开始,字符串常量池被移到了堆中的永久代(或 Java 8+ 的元空间),以减少方法区的内存占用。此外,在 Java 9 中,永久代被彻底移除,而字符串常量池等常量信息被存储在元空间中。因此,具体实现可能因 Java 版本和 JVM 实现而有所不同。
分析下面这几行代码:
public class Main {
public static void main(String[] args) {
String str1 = "ab";//1
String str2 = "ab";//2
String str3 = new String("ab");//3
String str4 = new String("ab");//4
System.out.println(str1==str2);//5
System.out.println(str1==str3);//6
System.out.println(str3==str4);//7
System.out.println(str4.equals(str3));//8
System.out.println(str2.equals(str1));//9
}
}
运行结果: true false false true true
我们逐一分析(序号以及数字分别和上面代码注释的编号对应):
1.“ab”这个字符串在字符串常量池中不存在,会在字符串常量池中创建一个字符串对象“ab”,记为 A
2.“ab”这个字符串在字符串常量池中存在,不会创建新的对象,而是地址指向 1 中创建的对象A的地址
3.因为用到了 new 关键字,会在堆中创建一个value 为“ab”的对象我们称这个对象为 B。需要注意的是,如果“ab”在字符串常量池中不存在,则也会在字符串常量池中创建一个字符串对象“ab”,然后将这个对象的在字符串常量池地址赋值给 B的在堆中保存的值的地址。而上面代码经历了 1 阶段,所以字符串常量池中存在“ab”,所以这一句只会在堆中创建一个“ab”字符串对象,然后将这个对象的值的地址赋值为字符串常量中”ab“的地址,即 A 的地址即下图:
4.“ab”在字符串常量池当中已经有对象,所以会在堆中创建一个对象C,并把这个对象的值的地址赋值为 A 的地址,当然 C 的地址和 B 的地址肯定是不一样的,所以输出 false,但他们的值的指向的地址是一样的
-
str1 和 str2 指向的是同一个对象,返回 true
-
str1 和 str3 指向的是不同的对象,返回 false
-
str3 和 str4 指向的对象是不同的,返回 false
8.String 重写了 equals 方法,所以只比较内容,返回 true
9.与 8 同理