Ответ 1
Это поведение происходит из-за интернирования. Поведение описано в документах для String#intern
(в том числе, почему оно появляется в вашем коде, даже если вы никогда не вызываете String#intern
):
Пул строк, первоначально пустой, поддерживается в частном порядке классом
String
.Когда вызывается метод
intern
, если пул уже содержит строку, равную этому объектуString
, как определено методомequals(Object)
, тогда возвращается строка из пула. В противном случае этот объектString
добавляется в пул и возвращается ссылка на этот объектString
.Из этого следует, что для любых двух строк
s
иt
,s.intern() == t.intern()
естьtrue
тогда и только тогда, когдаs.equals(t)
истинно.Все литералы и строковые константные выражения интернированы. Строковые литералы определены в §3.10.5 спецификации Java Language Specification.
Итак, например:
public class Test {
private String s1 = "Hi";
public static void main(String [] args) {
new Test().test();
System.exit(0);
}
public void test() {
String s2 ="Hi";
String s3;
System.out.println("[statics] s2 == s1? " + (s2 == s1));
s3 = "H" + part2();
System.out.println("[before interning] s3 == s1? " + (s3 == s1));
s3 = s3.intern();
System.out.println("[after interning] s3 == s1? " + (s3 == s1));
System.exit(0);
}
protected String part2() {
return "i";
}
}
Вывод:
[statics] s2 == s1? true [before interning] s3 == s1? false [after interning] s3 == s1? true
Пройдя через это:
- Литерал, назначенный
s1
, автоматически интернирован, поэтомуs1
заканчивается ссылкой на строку в пуле. - Литеральный, назначенный
s2
, также автоинтерминирован, и поэтомуs2
заканчивается, указывая на тот же экземплярs1
, на который указывает. Это прекрасно, даже если два бита кода могут быть полностью неизвестны друг другу, потому что экземпляры JavaString
неизменяемы. Вы не можете их изменить. Вы можете использовать такие методы, какtoLowerCase
, чтобы вернуть новую строку с изменениями, но исходный текст, который вы назвалиtoLowerCase
(и т.д.), Остается неизменным. Таким образом, они могут безопасно делиться между несвязанным кодом. - Мы создаем новый экземпляр
String
через операцию выполнения. Хотя новый экземпляр имеет ту же последовательность символов, что и интернированный, это отдельный экземпляр. Среда выполнения не создает динамически создаваемые строки автоматически, потому что есть затраты: работа по поиску строки в пуле. (В то время как при компиляции компилятор может взять эту стоимость на себя.) Итак, теперь у нас есть два экземпляра: одинs1
иs2
указывают на, а одинs3
указывает на. Таким образом, код показывает, чтоs3 != s1
. - Затем мы явно ставим
s3
. Возможно, это большая строка, которую мы планируем долго удерживать, и мы считаем, что она будет дублироваться в других местах. Поэтому мы принимаем работу по интернированию в обмен на потенциальную экономию памяти. Поскольку интернирование по определению означает, что мы можем вернуть новую ссылку, мы возвращаем результат обратно наs3
. - И мы можем видеть, что действительно,
s3
теперь указывает на тот же экземплярs1
иs2
на.