Seltsames Verhalten des String-Pools

72

Ich habe eine Frage zu einem seltsamen Verhalten des String-Pools. Ich verwende ==, um gleiche Zeichenfolgen zu vergleichen, um herauszufinden, ob sie im Pool sind oder nicht.

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() + "");
  }
}

Die Ausgabe ist:

true
false

Das ist eine große Überraschung für mich. Könnte jemand dies bitte erklären? Ich denke, etwas darüber findet zur Kompilierungszeit statt. Aber warum macht das Hinzufügen ""zu einem String überhaupt einen Unterschied?

Iozee
quelle
@ MarkoTopolnik Scheint mir das gleiche.
Johnchen902
5
@ MarkoTopolnik Ich wusste, dass die Frage etwas anders ist. Die Antwort lautet jedoch immer "XXX ist eine Konstante für die Kompilierungszeit, YYY nicht". Vielleicht hatte ich aber eine falsche Frage gewählt.
Johnchen902
1
@ Johnchen902 Ich stimme zu, aber Sie haben die falsche Frage als Duplikat gepostet :-)
Thihara
Möchten Sie wirklich Referenzen oder die zurückgegebenen Zeichenfolgen vergleichen?
Mare Infinitus
@ StareInfinitus ja, warum nicht? Es ist viel schneller als ein Vergleich mit Gleichen. Natürlich müssen Sie sicher sein, dass sich alle Zeichenfolgen im Pool befinden (z intern(). B. via ).
Iozee

Antworten:

110
"555" + ""

ist eine Kompilierungszeitkonstante , während

giveLiteralString() + ""

ist nicht. Daher wird erstere nur in die Zeichenfolgenkonstante "555" kompiliert und letztere in den tatsächlichen Methodenaufruf und die Verkettung, was zu einer neuen Zeichenfolgeninstanz führt.


Siehe auch JLS §3.10.5 (String Literals) :

Durch Verkettung zur Laufzeit berechnete Zeichenfolgen werden neu erstellt und sind daher unterschiedlich.

Marko Topolnik
quelle
Können Sie bitte ein Zitat für eine neue Zeichenfolgeninstanz angeben, falls eine Methode aufgerufen wird? irgendwelche JLS-Links?
Sanbhat
Das Code-Optimierungsprogramm, das vor / während der Kompilierung ausgeführt wird, wird möglicherweise bereits "555"+""zu einem einzelnen Zeichenfolgenobjekt kombiniert. "555" While befindet method()+""sich noch method()+""nach der Kompilierung.
Korashen
3
@sanbhat Klicken Sie in meiner Antwort auf "Kompilierungszeitkonstante".
Marko Topolnik
Seite Frage: Wenn Sie hinzufügen sind finalzu giveLiteralString(), wäre es etwas ändern?
Constantino Tsarouhas
@RandyMarsh Es würde nicht: Die Liste der zulässigen Ausdrücke für Konstanten zur Kompilierungszeit anzeigen.
Marko Topolnik
31

Nach dem Dekompilieren dieser Zeile

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

Ich habe diesen Bytecode bekommen

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

das ist äquivalent zu

  System.out.println(true);

Das bedeutet, dass Ausdruck "555" == "555" + ""zu Booleschen Werten kompiliert wird true.

Für giveLiteralString() == giveLiteralString() + ""javac wurde dieser Bytecode erstellt

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

das ist äquivalent zu

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

Das wird immer falsch sein, da wir hier 2 verschiedene Objekte vergleichen.

Evgeniy Dorofeev
quelle
2
"was immer false erzeugt" - Technisch gesehen ist StringBuilder nicht erforderlich , um einen nicht internierten String zu erzeugen. Es ist einfach so, dass es keinen guten Grund gibt, zu versuchen, einen internierten zu produzieren.
Hot Licks
1
Von der StringBuilder.toString-API - Ein neues String-Objekt wird zugewiesen und initialisiert, um die derzeit von diesem Objekt dargestellte Zeichenfolge zu enthalten. Dieser String wird dann zurückgegeben.
Evgeniy Dorofeev
Aber nichts sagt, dass es nicht interniert werden kann.
Hot Licks
1
@HotLicks Wenn es interniert war, konnte nicht garantiert werden, dass es tatsächlich neu war. Wenn der Zeichenfolgenpool diese Zeichenfolge bereits enthält, wird die alte Instanz von zurückgegeben intern.
Marko Topolnik
2
@HotLicks Ich habe es gelesen, als ein neues String-Objekt zurückgegeben wird. Das ist immer neu, kein Objekt aus dem Pool
Evgeniy Dorofeev
4

Im zweiten Fall hätte der Compiler erkennen können, dass + ""es sich um eine Art No-Op handelt, da ""ein Wert zur Kompilierungszeit bekanntermaßen eine Länge von Null hat. Der Compiler muss das Ergebnis jedoch weiterhin auf giveLiteralStringNull prüfen (da die Nullprüfung +im nicht optimierten Fall als Ergebnis der Operation erfolgen würde). Daher ist es am einfachsten, die Optimierung einfach nicht zu versuchen.

Infolgedessen generiert der Compiler Code, um die Verkettung durchzuführen, und eine neue Zeichenfolge wird erstellt.

Hot Licks
quelle
5
Der Compiler muss dem JLS entsprechen, der ausdrücklich angibt, dass das Ergebnis der Laufzeitverkettung eine neue Zeichenfolge ist.
Marko Topolnik
0

Verkettung der Kompilierungszeichenfolge Durch konstante Ausdrücke berechnete Zeichenfolgen werden zur Kompilierungszeit ausgeführt und als Konstanten oder Literale behandelt. Dies bedeutet, dass der Wert der Zeichenfolge oder des Ausdrucks zur Kompilierungszeit bekannt ist oder ausgewertet wird. Daher kann der Compiler denselben Wert im Zeichenfolgenpool überprüfen und denselben zurückgeben String-Objektreferenz.

Runtime Concatenation String-Ausdrücke, deren Werte bekannt sind oder beim Kompilieren nicht ausgewertet werden können, aber von der Eingabe oder Bedingung der Laufzeit abhängen, dann kennt der Compiler den Wert des Strings nicht und landet daher immer mit StringBuilder, um den String anzuhängen, und immer gibt eine neue Zeichenfolge zurück. Ich denke, dieses Beispiel wird es besser verdeutlichen.

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() + "");
  }
Himansu Meher
quelle