intern () ведет себя по-разному в Java 6 и Java 7 - PullRequest
54 голосов
/ 15 августа 2011
class Test {
    public static void main(String...args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1.intern());
        String s2 = "Goodmorning";
        if (s1 == s2) {
            System.out.println("both are equal");
        }
    }
}

Этот код создает различные выходные данные в Java 6 и Java 7. В Java 6 условие s1==s2 возвращает false, а в Java 7 s1==s2 возвращает true.Почему?

Почему эта программа производит различный вывод в Java 6 и Java 7?

Ответы [ 9 ]

26 голосов
/ 17 августа 2011

Кажется, что JDK7 обрабатывает интерна другим способом, как прежде.
Я протестировал его со сборкой 1.7.0-b147 и получил «оба равны», но при выполнении этого (тот же байт-код) с 1,6.0_24 я не получаю сообщение.
Это также зависит от того, где в исходном коде находится строка String b2 =.... Следующий код также не выводит сообщение:

class Test {
   public static void main(String... args) {
      String s1 = "Good";
      s1 = s1 + "morning";

      String s2 = "Goodmorning";
      System.out.println(s1.intern());  //just changed here s1.intern() and the if condition runs true   

      if(s1 == s2) {
         System.out.println("both are equal");
      } //now it works.
   }
}

похоже, что intern после того, как не найдет String в его пуле строк, вставляет фактический экземпляр s1 в пул. JVM использует этот пул при создании s2, поэтому он получает ту же ссылку, что и s1. С другой стороны, если сначала создается s2, эта ссылка сохраняется в пуле.
Это может быть результатом перемещения внутренних строк из постоянного поколения кучи Java.

Найдено здесь: Важные RFE, адресованные в JDK 7

В JDK 7 встроенные строки больше не выделяются в постоянном поколении кучи Java, а вместо этого выделяются в основной части кучи Java (известной как молодое и старое поколения) вместе с другими созданными объектами по заявке. Это изменение приведет к увеличению объема данных, находящихся в основной куче Java, и уменьшению объема данных в постоянной генерации, что может потребовать корректировки размеров кучи. Большинство приложений увидят только относительно небольшие различия в использовании кучи из-за этого изменения, но более крупные приложения, которые загружают много классов или интенсивно используют метод String.intern (), увидят более существенные различия.

Не уверен, что это ошибка и с какой версии ... JLS 3.10.5 заявляет

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

поэтому вопрос заключается в том, как интерпретируется существующее ранее, время компиляции или время выполнения: "Goodmorning" уже существует или нет?
Я предпочитаю, чтобы это было реализовано до 7 ...

24 голосов
/ 29 августа 2011

Давайте опускаем ненужные детали из примера:

class Test {
    public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

Давайте рассмотрим String#intern как черный ящик.Основываясь на нескольких тестовых случаях, я пришел бы к выводу, что реализация выглядит следующим образом:

Java 6:
, если пул содержит объект, равный this, затем возвращает ссылку на этот объект, иначе создайте новыйстрока (равная this), помещаемая в пул и возвращающая ссылку на этот созданный экземпляр.

Java 7:
, если пул содержит объект, равный this, затем возвращает ссылку на этот пул.объект, иначе поместите this в пул и верните this.

Ни Java 6, ни Java 7 не нарушают контракт метода .

Кажетсяэто новое поведение метода intern было результатом исправления этой ошибки: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6962931.

9 голосов
/ 15 августа 2011

== сравнивает ссылки. Метод intern гарантирует, что строки с одинаковым значением имеют одинаковые ссылки.

Javadoc для метода String.intern объясняет:

public String intern ()

Возвращает каноническое представление для строкового объекта.

Пул строк, изначально пустой, поддерживается в частном порядке класс String.

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

Следовательно, для любых двух строк s и t s.intern () == t.intern () имеет значение true, если и только если s.equals (t) имеет значение true.

Все литеральные строки и строковые константные выражения интернированы. Строковые литералы определены в §3.10.5 языка Java Спецификация

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

Таким образом, без интернирования компилятор смотрит на константы в коде Java и строит из этого свой постоянный пул. Класс String поддерживает другой пул, и интернирование проверяет строку, переданную в пул, и проверяет, является ли ссылка уникальной (так что == будет работать).

7 голосов
/ 17 августа 2011

В jdk6: String s1="Good"; создает объект String "Good" в постоянном пуле.

s1=s1+"morning"; создает другой объект String "morning" в постоянном пуле, но на этот раз JVM на самом деле: s1=new StringBuffer().append(s1).append("morning").toString();.

Теперь, когда оператор new создает объект в куче, следовательно, ссылка в s1 имеет пул кучи, а не постоянный пул, а String s2="Goodmorning"; создает объект String "Goodmorning" в пуле констант, чья ссылка сохраняетсяв s2.

Следовательно, if(s1==s2) условие ложно.

Но что происходит в jdk7?

6 голосов
/ 18 августа 2011

ПЕРВЫЙ ДЕЛО:

В первом фрагменте кода вы фактически добавляете три строки в пул строк. 1. s1 = "Хорошо"
2. s1 = "Доброе утро" (после объединения) 3. s2 = "Goodmorining"

При выполнении if (s1 == s2) объекты одинаковы, но ссылки различны, следовательно, это ложь.

ВТОРОЙ СЛУЧАЙ:

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

  1. s1 = "Хорошо"
  2. s1 = "Доброе утро" (после объединения)
  3. Для String s2 = "Goodmorning", новая String не добавляется в пул, и вы получаете ссылку на существующую для s2. Следовательно, если (s1 == s2) возвращает true.
5 голосов
/ 15 августа 2011

Вам нужно использовать s1.equals(s2).Использование == с String объектами сравнивает сами ссылки на объекты.

Редактировать: Когда я запускаю ваш второй фрагмент кода, я не получаю распечатки "оба равны".

Edit2: Уточнено, что ссылки сравниваются при использовании '=='.

4 голосов
/ 15 августа 2011

Есть в основном 4 способа сравнения строк:

  1. "== оператор": он просто сравнивает ссылочную переменную строкового объекта. Так что это может дать вам неожиданные результаты в зависимости от того, как вы создали строку, то есть с помощью конструктора класса String или просто с помощью двойных кавычек, поскольку оба получают память по-разному (в куче и пуле соответственно).
  2. «метод equals (Object)»: это метод класса объекта, который перезагружается строковым классом. Он сравнивает всю строку и чувствителен к регистру.
  3. «метод equalsIgnoreCase (String)»: это метод строкового класса, который сравнивает всю строку и НЕ ЧУВСТВИТЕЛЬНО К СЛУЧАЮ.
  4. «метод сравнения (String)»: сравнивать обе строки символ за символом и возвращать их разницу, если возвращаемое значение равно 0, это означает, что строки равны.
3 голосов
/ 15 августа 2011

Когда вы сравниваете две строки, не используйте == и не используйте eqauls(), потому что вы сравниваете объекты, а не ссылки:

string1.equals(string2);
2 голосов
/ 26 мая 2012

Время выполнения зависимых кодов результата:

class Test {
     public static void main(String... args) {
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints true for jdk7, false - for jdk6.
    }
}

Если вы напишите так:

class Test {
     public static void main(String... args) {
        String s = "GoodMorning";
        String s1 = "Good";
        s1 = s1 + "morning";
        System.out.println(s1 == s1.intern()); // Prints false for both jdk7 and jdk6.
    }
}

, причина в том, что 'ldc #N' (Загрузка строки из пула констант) и строка.intern () оба будут использовать StringTable в JVM горячей точки.Для деталей я написал статью на английском языке: http://aprilsoft.cn/blog/post/307.html

...