Seltsames String-Pool-Verhalten

Lesezeit: 5 Minuten

Benutzer-Avatar
Jozee

Ich habe eine Frage zu einem seltsamen String-Pool-Verhalten. Ich benutze == um gleiche Strings 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

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

  • @MarkoTopolnik Scheint mir gleich.

    – johnchen902

    8. Juli 2013 um 11:18 Uhr

  • @MarkoTopolnik Ich wusste, dass die Frage etwas anders ist. Aber die Antwort ist immer wie “XXX ist eine Kompilierzeitkonstante, YYY dagegen nicht”. Vielleicht hatte ich aber auch eine falsche Frage gewählt.

    – johnchen902

    8. Juli 2013 um 11:22 Uhr

  • @johnchen902 Ich stimme zu, aber du hast die falsche Frage als Duplikat gepostet 🙂

    – Thihara

    8. Juli 2013 um 12:25 Uhr

  • Möchten Sie wirklich Referenzen oder die zurückgegebenen Zeichenfolgen vergleichen?

    – Stute Infinitus

    22. Juli 2013 um 11:17 Uhr

  • @MareInfinitus Ja, warum nicht? Es ist viel schneller als das Vergleichen mit Gleichheit. Natürlich müssen Sie sicher sein, dass alle Saiten im Pool sind (zB via intern()).

    – Joze

    22. Juli 2013 um 12:02 Uhr


Benutzer-Avatar
Marco Topolnik

"555" + ""

ist ein Kompilierzeitkonstantewohingegen

giveLiteralString() + ""

ist nicht. Daher kompiliert Ersteres nur in die Zeichenfolgenkonstante “555” und Letzteres kompiliert in den eigentlichen Methodenaufruf und die Verkettung, was zu einer neuen String-Instanz führt.


Siehe auch JLS §3.10.5 (String-Literale):

Strings, die zur Laufzeit durch Verkettung berechnet werden, werden neu erstellt und sind daher eindeutig.

  • Können Sie bitte ein Zitat für eine neue Zeichenfolgeninstanz im Falle eines Methodenaufrufs angeben? Irgendwelche JLS-Links?

    – Sanbhat

    8. Juli 2013 um 11:18 Uhr


  • Auch der Codeoptimierer, der vor/während der Kompilierung ausgeführt wird, kann bereits kombiniert werden "555"+"" zu einem einzelnen String-Objekt "555" Während method()+"" wird immer noch sein method()+"" nach Zusammenstellung.

    – Koraschen

    8. Juli 2013 um 11:19 Uhr


  • @sanbhat Klicken Sie in meiner Antwort auf “Kompilierzeitkonstante”.

    – Marko Topolnik

    8. Juli 2013 um 11:20 Uhr


  • Nebenfrage: Wenn Sie hinzufügen würden final zu giveLiteralString()würde es etwas ändern?

    – Constantino Zarouhas

    10. Juli 2013 um 11:05 Uhr

  • @RandyMarsh Es würde nicht: Sehen Sie sich die Liste der zulässigen Ausdrücke für Konstanten zur Kompilierzeit an.

    – Marko Topolnik

    10. Juli 2013 um 11:11 Uhr

Benutzer-Avatar
Evgeniy Dorofeev

Nach dem Dekompilieren dieser Zeile

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

Ich habe diesen Bytecode

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

was äquivalent ist

  System.out.println(true);

das bedeutet Ausdruck "555" == "555" + "" kompiliert zu boolean true.

Zum giveLiteralString() == giveLiteralString() + "" Javac hat diesen 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
    ...

was äquivalent ist

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

was immer false ergibt, da wir hier 2 unterschiedliche Objekte vergleichen.

  • “was immer false produziert” — Technisch gesehen ist StringBuilder das nicht erforderlich um eine nicht-internierte Zeichenfolge zu erzeugen. Es gibt einfach keinen guten Grund dafür, zu versuchen, eine internierte zu erstellen.

    – Heiße Licks

    8. Juli 2013 um 11:29 Uhr

  • From StringBuilder.toString API – Ein neues String-Objekt wird zugewiesen und initialisiert, um die derzeit durch dieses Objekt dargestellte Zeichenfolge zu enthalten. Dieser String wird dann zurückgegeben.

    – Evgeniy Dorofeev

    8. Juli 2013 um 11:31 Uhr

  • Aber nichts sagt, dass es nicht interniert werden kann.

    – Heiße Licks

    8. Juli 2013 um 11:35 Uhr

  • @HotLicks Wenn es interniert wurde, kann nicht garantiert werden, dass es tatsächlich neu ist. Wenn der String-Pool diesen String bereits enthält, wird die alte Instanz von zurückgegeben intern.

    – Marko Topolnik

    8. Juli 2013 um 11:37 Uhr

  • @HotLicks Ich habe es gelesen, als ein neues String-Objekt zurückgegeben wird. Das ist immer neu, kein Objekt aus Pool

    – Evgeniy Dorofeev

    8. Juli 2013 um 11:39 Uhr


Im zweiten Fall hätte der Compiler das erkennen können + "" ist eine Art No-Op, da "" ist ein Wert zur Kompilierzeit, der bekanntermaßen eine Länge von Null hat. Aber der Compiler ist immer noch erforderlich, um das Ergebnis zu überprüfen giveLiteralString für null (da die Nullprüfung als Ergebnis der + Betrieb im nicht optimierten Fall), daher ist es am einfachsten, die Optimierung einfach nicht zu versuchen.

Als Ergebnis generiert der Compiler Code zum Ausführen der Verkettung, und es wird eine neue Zeichenfolge erstellt.

  • Der Compiler muss dem JLS entsprechen, das ausdrücklich besagt, dass das Ergebnis der Laufzeitverkettung ein frischer String ist.

    – Marko Topolnik

    8. Juli 2013 um 11:39 Uhr

Compile Time Concatenation
Durch einen konstanten Ausdruck berechnete Zeichenfolgen werden zur Kompilierzeit ausgeführt und als Konstanten oder Literale behandelt, was bedeutet, dass der Wert der Zeichenfolge oder des Ausdrucks zur Kompilierzeit bekannt ist oder ausgewertet wird, sodass der Compiler denselben Wert im Zeichenfolgenpool überprüfen und dieselbe Zeichenfolgenobjektreferenz zurückgeben kann .

Laufzeitverkettung
Zeichenfolgenausdrücke, deren Werte bekannt sind oder beim Kompilieren nicht ausgewertet werden können, aber von der Eingabe oder Laufzeitbedingung abhängen, kennt der Compiler den Wert der Zeichenfolge nicht und verwendet daher immer StringBuilder, um die Zeichenfolge anzuhängen, und gibt immer a zurück neue Saite. 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() + "");
  }

1179880cookie-checkSeltsames String-Pool-Verhalten

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy