为什么intern()不能与文字'java'一起使用?

时间:2018-03-27 21:07:52

标签: java string string-interning

我试过下面的代码:

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"?为什么当文字为true时,上述参考比较的评估结果为"java"除了

4 个答案:

答案 0 :(得分:16)

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

你的问题是在程序启动之前,常量字符串池中存在文字"java" - JVM只是将其用于某些用途。因此,在intern()上调用new String(new char[] {'j', 'a', 'v', 'a'})实习您的参考。相反,它从常量池返回预先存在的规范值,您很乐意忽略返回值。

您不应忽略返回值,而是使用它。你永远都不知道自JVM启动以来你的“绝对原创”字符串是否一直没有存在于常量池中。无论如何,所有这些都是依赖于实现的,你应该总是使用intern()方法返回的引用,或者永远不要。不要在它们之间混合。

答案 1 :(得分:3)

answer by Petr Janeček几乎肯定是正确的(+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"作为另一个测试用例。它显示了相同的行为,因为可以假设在引导VM之前已经出现了字符串true

答案 2 :(得分:1)

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

sj1 = sj1.intern();

答案 3 :(得分:1)

在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在执行String sc = "java"之前被截断到字符串池的内容。由于编译时间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

的正确方法