正在延迟加载字符串池。如果您在字符串文字之前自己调用 intern(),那么这就是将进入字符串池的字符串版本。如果您自己不调用 intern(),那么字符串文字将为我们填充字符串池。
令人惊讶的是,我们可以在常量池之前影响字符串池;如下面的代码 sn-ps 所示。
要了解为什么两个代码 sn-ps 具有不同的行为,重要的是要清楚
the constant pool is not the same as the string pool。也就是说,常量池是存储在磁盘上的类文件的一部分,而字符串池是填充了字符串的运行时缓存。
并且根据 Java 语言规范 jls-3.10.5,引用字符串文字不会直接引用常量池;当且仅当字符串池中还没有值时,字符文字才会从常量池中填充字符串池。
也就是说,一个String对象从源文件到运行时的生命周期如下:
- 在编译时由编译器放入常量池,并存储在生成的类文件中(每个类文件有一个常量池)
- 常量池由 JVM 在类加载时加载
- 从常量池创建的字符串在运行时被添加到字符串池中,因为 intern 被调用(如果等效字符串不存在,如果已经存在字符串,则将使用字符串池中的字符串) JVM Spec 5.1 - The Run-Time Constant Pool。
- intern 可以通过手动调用 intern() 显式发生,也可以通过引用诸如“abc”jls-3.10.5 之类的字符串文字隐式发生。
以下两个代码 sn-ps 之间的行为差异是由于在通过字符串字面量对 intern 的隐式调用发生之前显式调用 intern() 引起的。
为了清楚起见,这里是在 cmets 中讨论到这个答案的两种行为:
String c = "qwe"; // string literal qwe goes into runtime cache
String d = c.substring(1); // runtime string "we" is created
d.intern(); // intern "we"; it has not been seen
// yet so this version goes into the cache
String e = "we"; // now we see the string literal, but
// a value is already in the cache and so
// the same instance as d is returned
// (see ref below)
System.out.println( e == d ); // returns true
下面是我们在使用字符串文字后实习时发生的情况:
String c = "qwe"; // string literal qwe goes into runtime cache
String d = c.substring(1); // runtime string "we" is created
String e = "we"; // now we see the string literal, this time
// a value is NOT already in the cache and so
// the string literal creates an object and
// places it into the cache
d.intern(); // has no effect - a value already exists
// in the cache, and so it will return e
System.out.println( e == d ); // returns false
System.out.println( e == d.intern() ); // returns true
System.out.println( e == d ); // still returns false
下面是 JLS 的关键部分,说明 intern 被隐式调用用于字符串文字。
此外,字符串字面量总是引用 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用 String.intern 方法共享唯一实例。
JVM 规范涵盖了从类文件加载的常量池的运行时表示的详细信息,并与实习生交互。
如果先前已在包含与 CONSTANT_String_info 结构给出的相同的 Unicode 代码点序列的类 String 的实例上调用方法 String.intern,则字符串字面量推导的结果是对同一实例的引用类字符串。
.