Java 中的字符串实习是否在编译时完成?

Is string interning done at compile time in Java?

我真的对 string interning 在 Java 中的工作方式感到困惑。当我写:

String a = "ABC";
String b = "ABC";

if (a==b)
    System.out.println("Equal");

编译器是否在编译时将字符串文字"ABC"存储到字符串常量池中?

这听起来不合逻辑,因为我认为字符串常量池是由 JVM 在运行时创建的,而且我不明白如果它在编译时完成怎么可能,因为 Java 编译器会甚至不调用 JVM。

如果不是在编译时完成,而是在运行时完成,那么为什么以下 return 错误(取自 this answer)?

// But .substring() is invoked at runtime, generating distinct objects
"test" == "!test".substring(1) // --> false

如果它是在运行时完成的,那么为什么 JVM 不能确定它们是相同的字符串?

我真的很困惑 Java 中字符串实习的工作方式以及 Java 字符串池的确切存储位置。

编译器将文字字符串放入class文件中(并且只有唯一的,它合并了所有等价的文字); JVM 在加载 class 文件时将这些字符串加载到字符串池中。

If it is done at runtime then why can't the JVM figure out that they are the same String.

因为 .substring 返回的字符串尚未被驻留,因此与字符串池中等效的 "test" 字符串是不同的对象。如果你实习它,你会得到 true:

"test" == "!test".substring(1).intern() // true

部分 §4.4 of the JLS and §5.3 of the JVM spec 看起来相关。


需要说明的是:在 Java 中比较字符串的正确方法是使用 .equals 方法或类似方法,而不是 ==。将 == 与字符串实例一起使用 通常 是不正确的。 (除非你正在玩弄理解事物何时以及如何被拘禁...)

我检查了 .class

String a = "ABC";
String b = "ABC";

发现其中只有一个 "ABC"。即 javac 在编译时创建一个相同字符串的常量。

但如果 2 个或更多 class 具有相同的 "ABC" 常量,则 JVM 会将它们放在字符串池中的相同位置