为什么 intern() 不适用于文字 'java'?

Why intern() does not work with literal 'java'?

我试过下面的代码:

public class TestIntern {
  public static void main(String[] args) {
   char[] c1={'a','b','h','i'};
   String s1 = new String(c1);
   s1.intern();
   String s2="abhi";
   System.out.println(s1==s2);//true

   char[] c2={'j','a','v','a'};
   String sj1 = new String(c2);
   sj1.intern();
   String sj2="java";
   System.out.println(sj1==sj2);//false

   char[] c3={'J','A','V','A'};
   String tj1 = new String(c3);
   tj1.intern();
   String tj2="JAVA";
   System.out.println(tj1==tj2);//true
  }
}

我尝试了很多不同的文字。

任何人都可以解释为什么 intern() 不能按预期使用文字 "java" 吗?为什么当字面量为 "java" 时,上述参考比较的计算结果为 trueexcept

您没有正确使用 internintern 不会修改它所调用的字符串对象(无论如何字符串都是不可变的),但是 returns 是该字符串的规范表示 - 您只是丢弃了它。相反,您应该将其分配给一个变量并在检查中使用该变量。例如:

sj1 = sj1.intern();

当 JVM 第一次遇到 new String(new char[] {'a', 'b', 'h', 'i'}) 字符串并且您对其调用 intern() 时,您刚刚创建的引用成为规范引用并存储在字符串常量池中。然后 "abhi" 从常量池中拉出 - 你的规范实例已被重用。

您的问题是在您的程序开始之前,文字 "java" 存在于常量字符串池中 - JVM 只是将它放在那里以供某些使用。因此,在 new String(new char[] {'j', 'a', 'v', 'a'}) 上调用 intern() 不会 而不是 实习你的参考。相反,它 return 是常量池中的 pre-existing 规范值,您可以愉快地忽略 return 值。

您不应忽略 return 值,而应使用它。你永远不知道自从 JVM 启动以来,你的 "definitely original" 字符串是否一直存在于常量池中。无论如何,所有这些都依赖于实现,您应该始终使用由 intern() 方法编辑的 return 引用,或者从不使用。请勿混用。

在 OpenJDK 1.8.0u151 和 OpenJDK 9.0.4 上

char[] cj = {'j','a','v','a'};
String sj = new String(cj);
sj.intern();
String sc = "java";
System.out.println(sj == sc); 

打印 true。但是,此 == 检查取决于在执行 String sc = "java" 之前,哪些 String 已被驻留在字符串池中。由于编译时 String 常量由 Java 编译器驻留,因此 sc 引用现在指向字符串池中的 "java",它与 sj.intern() 一起使用 s1参考。

如果你尝试分配 String "java" 之前喜欢:

String before = "java"; // interned before by compiler
char[] cj = {'j','a','v','a'};
String sj = new String(cj);
sj.intern();
String sc = "java";
System.out.println(sj == sc);

代码现在将打印 false,因为 sj.intern() 现在没有副作用,因为 "java" String 之前已被实习。

要调试您的问题,请在到达失败检查之前检查驻留字符串池中的内容。这可能取决于您的 JVM 供应商或版本。

有人会争辩说调用 intern() 只是为了将值添加到字符串池中的副作用是毫无意义的。写sj = sj.intern()才是实习String.

的正确方法

几乎可以肯定是正确的(+1)。

真的证明它很难,因为大部分字符串池都驻留在 JVM 本身中,如果没有经过调整的 VM 几乎无法访问它。

但这里还有更多证据:

public class TestInternEx
{
    public static void main(String[] args)
    {
        char[] c1 = { 'a', 'b', 'h', 'i' };
        String s1 = new String(c1);
        String s1i = s1.intern();
        String s1s = "abhi";
        System.out.println(System.identityHashCode(s1));
        System.out.println(System.identityHashCode(s1i));
        System.out.println(System.identityHashCode(s1s));
        System.out.println(s1 == s1s);// true

        char[] cj =
        { 'j', 'a', 'v', 'a' };
        String sj = new String(cj);
        String sji = sj.intern();
        String sjs = "java";
        System.out.println(System.identityHashCode(sj));
        System.out.println(System.identityHashCode(sji));
        System.out.println(System.identityHashCode(sjs));
        System.out.println(sj == sjs);// false

        char[] Cj = { 'J', 'A', 'V', 'A' };
        String Sj = new String(Cj);
        String Sji = Sj.intern();
        String Sjs = "JAVA";
        System.out.println(System.identityHashCode(Sj));
        System.out.println(System.identityHashCode(Sji));
        System.out.println(System.identityHashCode(Sjs));
        System.out.println(Sj == Sjs);// true

        char[] ct =
        { 't', 'r', 'u', 'e' };
        String st = new String(ct);
        String sti = st.intern();
        String sts = "true";
        System.out.println(System.identityHashCode(st));
        System.out.println(System.identityHashCode(sti));
        System.out.println(System.identityHashCode(sts));
        System.out.println(st == sts);// false


    }
}

程序为每个字符串打印

的身份哈希码
  • new String
  • 创建的字符串
  • String#intern
  • 返回的字符串
  • 作为文字给出的字符串

输出是这样的:

366712642
366712642
366712642
true
1829164700
2018699554
2018699554
false
1311053135
1311053135
1311053135
true
118352462
1550089733
1550089733
false

可以看出,对于字符串 "java"new String 的哈希码与字符串文字的哈希码 不同,但是后者与调用 String#intern 的结果 相同 - 这意味着 String#intern 确实返回了一个与文字本身完全相同的字符串。

我还添加了字符串 "true" 作为另一个测试用例。它显示了相同的行为,因为可以假设字符串 true 在引导 VM 之前已经出现。