it-swarm.com.ru

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?

54
Mohammad Faisal

Кажется, что 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 заявляет

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

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

26
Carlos Heuberger

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

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 .

24
Andrey

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

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

public String intern ()

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

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

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

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

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

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

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

9
Nathan Hughes

В 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?

7
Mohammad Faisal

Первый случай:

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

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

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

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

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

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

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

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

5
Datajam

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

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

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

string1.equals(string2);
3
Eng.Fouad

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

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' (строка загрузки из постоянного пула) и String.intern () оба будут использовать StringTable в JVM горячей точки. Для подробностей я написал статью на английском языке: http://aprilsoft.cn/blog/post/307.html

2
Hatter Jiang