Почему intern() не работает с буквальным "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"? Почему приведенные выше сравнительные сравнения оцениваются как true, за исключением случаев, когда литерал является "java"?

Ответ 1

Когда JVM впервые встречает new String(new char[] {'a', 'b', 'h', 'i'}) и вы вызываете intern() на нее, ссылка, которую вы только что создали, становится канонической и сохраняется в пуле постоянной строки. Затем "abhi" вытаскивается из постоянного пула - ваш канонический экземпляр был повторно использован.

Ваша проблема в том, что буквальный "java" существует в пуле строк до начала вашей программы - JVM просто имеет его там для некоторого использования. Таким образом, вызов intern() в new String(new char[] {'j', 'a', 'v', 'a'}) не ставит вашу ссылку. Вместо этого он возвращает ранее существующее каноническое значение из пула констант, и вы счастливо игнорируете возвращаемое значение.

Вы не должны игнорировать возвращаемое значение, но используйте его. Вы никогда не знаете, не существовала ли ваша "определенно оригинальная" строка в постоянном пуле с момента запуска JVM. В любом случае, все это зависит от реализации, вы должны либо всегда использовать ссылки, возвращаемые методом intern(), либо никогда. Не смешивайте их между собой.

Ответ 2

Ответ Петра Янечка почти наверняка правильный (+1).

Действительно доказать это сложно, потому что большая часть пула строк находится в самой JVM, и вряд ли можно получить доступ к ней без измененной виртуальной машины.

Но вот еще несколько доказательств:

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 отличается от хэш- new String строкового литерала, но последний совпадает с тем, что для результата вызова String#intern - это означает, что String#intern действительно вернул строку, которая глубоко идентична самому String#intern.

Я также добавил строку "true" качестве другого тестового примера. Он показывает то же поведение, потому что можно предположить, что строка true уже появилась перед загрузкой виртуальной машины.

Ответ 3

Вы не используете intern правильно. intern не изменяет строковый объект, о котором он звонил (строки неизменны в любом случае), но возвращает каноническое представление этой строки, которое вы просто отбрасываете. Вместо этого вы должны назначить его переменной и использовать эту переменную в своих проверках. Например:

sj1 = sj1.intern();

Ответ 4

В 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 Pool до того, как 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() теперь не будет иметь никаких побочных эффектов, поскольку ранее была интернирована String "java".

Чтобы отладить свою проблему, проверьте, что внутри интернированного пула строк, прежде чем вы достигнете ошибки проверки. Это может зависеть от вашего поставщика или версии JVM.

Можно было бы утверждать, что вызов intern() только для побочного эффекта добавления значения в пул строк бессмыслен. Запись sj = sj.intern() - это правильный способ sj = sj.intern() String.