Почему 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
.