Подтвердить что ты не робот

Intern() ведет себя по-разному в Java 6 и Java 7

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?

4b9b3361

Ответ 1

Кажется, что 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 после того, как не нашел строку в пуле строк, вставляет фактический экземпляр s1 в пул. JVM использует этот пул при создании s2, поэтому он получает ту же ссылку, что и s1. С другой стороны, если s2 создается первым, эта ссылка сохраняется в пуле.
Это может быть результатом перемещения интернированных строк из постоянного поколения кучи Java.

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

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

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

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

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

Ответ 2

Опустить ненужные данные из примера:

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, то верните ссылку на этот объект, else положите this в пул и верните this.

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

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

Ответ 3

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

В javadoc для метод String.intern объясняется:

public String intern()

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

ПЕРВЫЙ СЛУЧАЙ:

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

Выполняя if (s1 == s2), объекты одинаковы, но ссылка как другая, поэтому она ложна.

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

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

  • s1 = "Хорошо"
  • s1 = "Goodmorning" (после конкатенации)
  • Для String s2 = "Goodmorning" новая строка не добавляется в пул и вы получаете ссылку на существующий для s2. Следовательно, если (s1 == s2) возвращает true.

Ответ 6

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

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

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

Ответ 7

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

  • "== operator": он просто сравнивает ссылочную переменную строкового объекта. Таким образом, это может дать вам неожиданные результаты в зависимости от того, как вы создали строку, например, с помощью конструктора классов String или просто с помощью двойной кавычки, поскольку и получить память по-разному (в куче и пуле соответственно).
  • "equals (Object) method": это метод класса объекта и OVERLOADED строковым классом. Он сравнивает целую строку и IS CASE SENSITIVE.
  • "equalsIgnoreCase (String) method": это метод строкового класса и сравнивает целую строку и НЕ ИСПРАВЛЯЕТСЯ ЧУВСТВИТЕЛЬНЫМ.
  • "сравнивает (String) метод": сравнивает оба символа строки по символу и возвращает их разность, если возвращаемое значение равно 0, это означает, что строки равны.

Ответ 8

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

string1.equals(string2);

Ответ 9

Идентификатор кода результата:

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.
    }
}

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