Объяснение возврата String.intern()

Учитывать:

String s1 = new StringBuilder("Cattie").append(" & Doggie").toString();
System.out.println(s1.intern() == s1); // true why?
System.out.println(s1 == "Cattie & Doggie"); // true another why?

String s2 = new StringBuilder("ja").append("va").toString();
System.out.println(s2.intern() == s2); // false

String s3 = new String("Cattie & Doggie");
System.out.println(s3.intern() == s3); // false
System.out.println(s3 == "Cattie & Doggie"); // false

Я запутался, почему они получаются по-разному из-за возвращаемого значения String.intern(), в котором говорится:

Если при вызове внутреннего метода пул уже содержит строку, равную этому объекту String, определенному методом equals(Object), то возвращается строка из пула. В противном случае этот объект String добавляется в пул и возвращается ссылка на этот объект String.

Особенно после этих двух тестов:

assertFalse("new String() should create a new instance", new String("jav") == "jav");
assertFalse("new StringBuilder() should create a new instance",
    new StringBuilder("jav").toString() == "jav");

Я когда-то читал пост, в котором говорилось о каком-то special strings интернированном прежде всего, но теперь это настоящее размытие.

Если есть несколько строк pre-interned, есть ли способ получить их список? Мне просто интересно, какими они могут быть.


Обновлено

Благодаря помощи @Eran и @Slaw я, наконец, могу объяснить, что только что произошло для вывода.

true
true
false
false
false
  1. Поскольку "Cattie & Doggie" не существует в пуле, s1.intern() поместит текущую ссылку на объект в пул и вернет себя, поэтому s1.intern() == s1;
  2. "Cattie & Doggie" уже в пуле, поэтому строковый литерал "Cattie & Doggie" будет просто использовать ссылку в пуле, которая на самом деле s1, так что снова у нас есть true;
  3. new StringBuilder().toString() создаст новый экземпляр, пока "java" уже находится в пуле, а затем ссылка в пуле будет возвращена при вызове s2.intern(), поэтому s2.intern() != s2 и у нас есть false;
  4. new String() также вернет новый экземпляр, но когда мы попытаемся s3.intern(), он вернет ранее сохраненную ссылку в пуле, которая на самом деле s1, поэтому s3.intern() != s3 и у нас есть false;
  5. Как уже обсуждалось № 2, строковый литерал "Cattie & Doggie" вернет ссылку, уже сохраненную в пуле (которая на самом деле s1), поэтому s3 != "Cattie & Doggie" и у нас снова false.

Спасибо за @Sunny, чтобы предоставить трюк, чтобы получить все строки interned.


person Hearen    schedule 13.03.2019    source источник


Ответы (3)


s2.intern() будет возвращать экземпляр, на который ссылается s2, только если пул строк не содержит String, значение которого равно "java" до этого вызова. Классы JDK интернируют несколько String перед выполнением вашего кода. "java" должен быть одним из них. Поэтому s2.intern() возвращает ранее интернированный экземпляр вместо s2.

С другой стороны, классы JDK не интернировали никаких String, значение которых равно "Cattie & Doggie", поэтому s1.intern() возвращает s1.

Я не знаю ни одного списка предварительно интернированных строк. Такой список, скорее всего, будет рассматриваться как детали реализации, которые могут различаться в разных реализациях JDK и версиях JDK, и на них нельзя полагаться.

person Eran    schedule 13.03.2019
comment
спасибо за подробное объяснение, так правильно ли сказать: s.intern() вернет исходную ссылку, если строка не интернирована, но если она уже интернирована (в пуле констант), то она вернет ссылку в пуле констант ? - person Hearen; 13.03.2019
comment
@Hearen, это правда, как говорится в javadoc. Когда вызывается внутренний метод, если пул уже содержит строку, равную этому объекту String, как определено методом equals (Object), тогда возвращается строка из пула. В противном случае этот объект String добавляется в пул и возвращается ссылка на этот объект String. - person Eran; 13.03.2019
comment
извините, что снова вас побеспокоил. Если это так, то почему второе почему по-прежнему возвращает true? ... так запутанно... но когда я заменил new StringBuilder().toString() на new String(), они оба станут false. Так странно... - person Hearen; 13.03.2019
comment
Я обновил свой вопрос, есть another why. Извините за неудобства, пожалуйста, проверьте обновление - person Hearen; 13.03.2019
comment
@Hearen О, теперь я вижу - System.out.println(s1 == "Cattie & Doggie"); возвращает true, потому что s1 содержит интернированный экземпляр, равный "Cattie & Doggie". (из-за предыдущего вызова s1.intern()). Строковый литерал "Cattie & Doggie" не приводит к созданию новой строки, если в пуле уже присутствует эквивалентная строка. - person Eran; 13.03.2019
comment
но нет явного назначения, так как s1 = s1.intern() не должно s1 по-прежнему ссылка, указывающая на new StringBuilder() созданный экземпляр? - person Hearen; 13.03.2019
comment
@Hearen s1.intern() == s1 уже истинно (поскольку s1.intern() добавил экземпляр, на который ссылается s1, в пул), поэтому нет необходимости назначать s1.intern() s1. - person Eran; 13.03.2019
comment
Давайте продолжим это обсуждение в чате. - person Hearen; 13.03.2019
comment
@Hearen В противном случае этот объект String добавляется в пул и возвращается ссылка на этот объект String. Когда вы вызвали s1.intern(), экземпляр s1 был помещен в пул. Затем вы позже используете строковый литерал с тем же значением, что и s1, что означает, что он использует этот экземпляр из пула. - person Slaw; 13.03.2019
comment
@Слав Ха! Теперь я понял вашу точку зрения, я обновлю вопрос, чтобы подвести итоги. Спасибо Слав! Спасибо, Эран. - person Hearen; 13.03.2019
comment
любой список предварительно интернированных строк будет зависеть от того, какие классы JDK также загружаются вашей программой. - person Alexey Romanov; 13.03.2019
comment
@AlexeyRomanov и пусковая установка, например. обычно используемый стандартный модуль запуска загружает указанный основной класс и выполняет для него getMethod("main", String[].class), тем самым «предварительно интернируя» строку "main". Другой лаунчер, например. собственный модуль запуска, вызывающий основной метод через JNI, будет вести себя иначе. Точно так же способ обработки параметров командной строки может отличаться и, следовательно, по-разному влиять на список «предварительно интернированных» строк. - person Holger; 13.03.2019
comment
Я немного удивлен, что литерал "Cattie & Doggie" не интернируется сразу после загрузки класса. Думаю, мне нужно снова освежить в памяти внутренности JVM. - person Ilmari Karonen; 13.03.2019

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

Таким образом, строка java уже должна быть в пуле. следовательно, это дает ложь.

Вы можете распечатать все строки в пуле

Как распечатать весь пул строк?

Вот пример для получения вся строка, если вы используете openjdk.

person Sunny    schedule 13.03.2019
comment
Я только что попробовал с примером в github, который вы приложили, кажется, он не работает, хотя я добавил требуемую зависимость $JAVA_HOME/lib/sa-jdi.jar. Что касается ссылки на ОС, Как напечатать весь пул строк?, она еще не проверена, но выглядит очень сложно. Спасибо за помощь :) - person Hearen; 13.03.2019

Строковые литералы (те, которые жестко закодированы как «строка») уже интернированы для вас компилятором. Но те строки, которые получены программно, не являются интернированными и будут интернированы, только если вы используете метод .intern().

Обычно вы не интернируете строки вручную, если не знаете, что будете хранить в памяти большое количество повторяющихся строк, поэтому таким образом вы можете сэкономить много памяти.

Это объясняется здесь: Что такое интернирование строк Java?

person maslan    schedule 13.03.2019