интерн() ведет себя по-разному в 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?

9 54

9 ответов:

кажется, что 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.

найти здесь: важные RFEs, адресованные в JDK 7

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

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

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

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

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

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 не разбивают контракт метода.

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

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

javadoc для строку.интерн метод объясняю:

public String intern ()

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

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

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

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

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

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

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

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

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

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

таким образом, if(s1==s2) состояние ложный.

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

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

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

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

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

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

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

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

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

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

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

  1. "== operator": он просто сравнивает ссылочную переменную строкового объекта. Так что может дать вам неожиданные результаты в зависимости от того, как вы создали строку, т. е. используя конструктор класса String или просто с помощью двойных кавычек, как сделать память по-разному(в куче и бассейн соответственно).
  2. " метод equals (Object)": это метод класса объекта и перегружается строковым классом. Он сравнивает вся строка и чувствительна к регистру.
  3. " метод equalsIgnoreCase (String)": это метод класса string и сравнивает всю строку и не учитывает регистр.
  4. "compares (String) method": сравните обе строки по символу и верните их разницу, если возвращаемое значение равно 0, это означает, что строки равны.

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

string1.equals(string2);

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

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