Странное поведение пула строк

У меня вопрос о каком-то странном строковом пуле. Я использую == для сравнения равных строк, чтобы узнать, находятся ли они в пуле или нет.

public class StringPoolTest {
  public static void main(String[] args) {
    new StringPoolTest().run();
  }

  String giveLiteralString() {
    return "555";
  }

  void run() {
    String s1 = giveLiteralString() + "";
    System.out.println("555" == "555" + "");
    System.out.println(giveLiteralString() == giveLiteralString() + "");
  }
}

Вывод:

true
false

что является большим сюрпризом для меня. Может ли кто-нибудь объяснить это, пожалуйста? Я думаю, что что-то об этом происходит во время компиляции. Но почему добавление "" в String вообще не имеет значения?

Ответ 1

"555" + ""

является константой времени компиляции, тогда как

giveLiteralString() + ""

нет. Поэтому первая компилируется только в строчную константу "555", а последняя компилируется в фактический вызов и конкатенацию метода, в результате чего появляется новый экземпляр String.


Также см. JLS §3.10.5 (Строковые литералы):

Строки, вычисленные путем конкатенации во время выполнения, создаются и поэтому различны.

Ответ 2

После декомпиляции этой строки

System.out.println("555" == "555" + "");

Я получил этот байт-код

    LINENUMBER 8 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ICONST_1
    INVOKEVIRTUAL java/io/PrintStream.println(Z)V
    ...

что эквивалентно

  System.out.println(true);

то есть выражение "555" == "555" + "" компилируется в boolean true.

Для giveLiteralString() == giveLiteralString() + "" javac построил этот байт-код

    LINENUMBER 8 L0
    INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
    NEW java/lang/StringBuilder
    DUP
    INVOKESTATIC Test1.giveLiteralString()Ljava/lang/String;
    INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
    IF_ACMPNE L1
    ...

что эквивалентно

if (giveLiteralString() == new StringBuilder(giveLiteralString()).append("").toString()) {
...

который всегда будет генерировать false, так как здесь мы сравниваем 2 объекта disctinct.

Ответ 3

Во втором случае компилятор COULD признал, что + "" - это не-op сортировок, так как "" - это значение времени компиляции, которое известно как нулевая длина. Но компилятор по-прежнему должен проверять результат от giveLiteralString на нуль (поскольку нулевая проверка произойдет в результате операции + в неоптимизированном случае), поэтому проще просто не пытаться оптимизировать.

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

Ответ 4

Комбинация времени компиляции Строка, вычисленная постоянным выражением, выполняется во время компиляции и рассматривается как константы или литералы, означает, что значение строки или выражения известно или оценивается во время компиляции, поэтому компилятор может проверять одно и то же значение в пуле строк и возвращать ту же ссылку на строковый объект.

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

public static void main(String[] args) {
    new StringPoolTest().run();
  }
  String giveLiteralString() {
    return "555";
  }

  void run() {
    System.out.println("555" + 9 == "555" + 9);  
    System.out.println("555"+Integer.valueOf(9) == "555" + Integer.valueOf(9)); 
    System.out.println(giveLiteralString() == giveLiteralString());
    // The result of runtime concatenation is a fresh string.
    System.out.println(giveLiteralString() == giveLiteralString() + "");
  }