Ich habe eine einfache Frage zu Zeichenfolgen in Java. Das folgende Segment einfachen Codes verkettet nur zwei Zeichenfolgen und vergleicht sie dann mit ==
.
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
Der Vergleichsausdruck concat=="string"
kehrt false
als offensichtlich (ich verstehe den Unterschied zwischen equals()
und ==
).
Wenn diese beiden Zeichenfolgen so deklariert final
sind,
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
Der Vergleichsausdruck concat=="string"
gibt in diesem Fall zurück true
. Warum macht final
das einen Unterschied? Hat es etwas mit dem Praktikantenpool zu tun oder werde ich nur irregeführt?
equals()
und==
im Kontext von Zeichenfolgen und stellt eine aussagekräftigere Frage.String
? Ich denke, es ist sehr logisch, nicht albern, den Inhaltsvergleich durchführen zu lassenequals
, eine Methode, die wir überschreiben können, um festzustellen, wann zwei Objekte gleich sind, und um den Identitätsvergleich durchführen zu lassen==
. Wenn der Inhaltsvergleich von durchgeführt würde==
, könnten wir dies nicht überschreiben, um zu definieren, was wir unter "gleichem Inhalt" verstehen, und die Bedeutung vonequals
und==
nur fürString
s umgekehrt zu haben, wäre dumm. Unabhängig davon sehe ich ohnehin keinen Vorteil darin,==
stattdessen den Inhaltsvergleich durchzuführenequals
.Antworten:
Wenn Sie eine
String
( unveränderliche ) Variable als deklarierenfinal
und sie mit einem Konstantenausdruck zur Kompilierungszeit initialisieren, wird sie auch zu einem Konstantenausdruck zur Kompilierungszeit, und ihr Wert wird vom Compiler, in dem sie verwendet wird, eingefügt. In Ihrem zweiten Codebeispiel wird die Zeichenfolgenverkettung nach dem Inlinieren der Werte vom Compiler in Folgendes übersetzt:was im Vergleich zu
"string"
gibttrue
, weil String-Literale interniert sind .Aus JLS §4.12.4 -
final
Variablen :Auch aus JLS §15.28 - Konstanter Ausdruck:
Dies ist in Ihrem ersten Codebeispiel nicht der Fall, in dem die
String
Variablen nicht vorhanden sindfinal
. Sie sind also keine konstanten Ausdrücke zur Kompilierungszeit. Der dortige Verkettungsvorgang wird bis zur Laufzeit verzögert, wodurch ein neuesString
Objekt erstellt wird. Sie können dies überprüfen, indem Sie den Bytecode beider Codes vergleichen.Das erste Codebeispiel (Nichtversion
final
) wird mit dem folgenden Bytecode kompiliert:Es ist klar, dass es in zwei separaten Variablen gespeichert
str
unding
verwendet wirdStringBuilder
, um die Verkettungsoperation auszuführen.Ihr zweites Codebeispiel (
final
Version) sieht folgendermaßen aus:Daher wird die endgültige Variable direkt eingefügt, um
string
zur Kompilierungszeit einen String zu erstellen, der durch dieldc
schrittweise Operation geladen wird0
. Dann wird das zweite String-Literal durchldc
schrittweise Operation geladen7
. ZurString
Laufzeit wird kein neues Objekt erstellt. Der String ist bereits zur Kompilierungszeit bekannt und wird interniert.quelle
true
String
Objekt wird neu erstellt (§12.5), es sei denn, der Ausdruck ist ein konstanter Ausdruck (§15.28). Das Anwenden einer Optimierung in der nicht endgültigen Version ist buchstäblich nicht zulässig, da eine neu erstellte Zeichenfolge eine andere Objektidentität haben muss. Ich weiß nicht, ob das beabsichtigt ist. Schließlich besteht die aktuelle Kompilierungsstrategie darin, an eine Laufzeiteinrichtung zu delegieren, die solche Einschränkungen nicht dokumentiert.Nach meinen Recherchen sind alle
final String
in Java interniert. Aus einem der Blog-Beiträge:Wenn Sie also aufrufen
String.intern()
, können Sie zwei Zeichenfolgen mit dem==
Operator vergleichen. Dies ist hierString.intern()
jedoch nicht erforderlich, da in Javafinal String
intern interniert sind.Weitere Informationen finden Sie unter String-Vergleich mit dem Operator == und der Methode Javadoc für String.intern () .
Weitere Informationen finden Sie auch in diesem Stackoverflow- Beitrag.
quelle
Wenn Sie sich diese Methoden ansehen
und
javap -c ClassWithTheseMethods
es wird mit Versionen dekompiliert, die Sie sehen werdenund
Wenn Strings also nicht endgültig sind, muss der Compiler
StringBuilder
zum Verketten verwendenstr1
undstr2
so weiterwird zu kompiliert
Dies bedeutet, dass
concat
es zur Laufzeit erstellt wird und nicht aus dem String-Pool stammt.Auch wenn Strings endgültig sind, kann der Compiler davon ausgehen, dass sie sich niemals ändern, sodass er anstelle seiner Verwendung
StringBuilder
seine Werte sicher verketten kannkann geändert werden zu
und verkettet in
Dies bedeutet, dass dies
concate
zu einem Sting-Literal wird, das im String-Pool interniert und dann mit demselben String-Literal aus diesem Pool in derif
Anweisung verglichen wird .quelle
Pool-Konzept für Stapel- und String-Conts
quelle
Sehen wir uns einen Bytecode für das
final
Beispiel anBei
0:
und2:
wird dasString
"string"
auf den Stapel (aus dem konstanten Pool) geschoben undconcat
direkt in der lokalen Variablen gespeichert . Sie können daraus schließen, dass der Compiler zur Kompilierungszeit das Selbst erstellt (verkettet)String
"string"
.Der Nicht-
final
Byte-CodeHier haben Sie zwei
String
Konstanten,"str"
und"ing"
die müssen zur Laufzeit mit einer verketteten werdenStringBuilder
.quelle
Wenn Sie mit der String-Literal-Notation von Java erstellen, wird automatisch die intern () -Methode aufgerufen, um dieses Objekt in den String-Pool einzufügen, sofern es nicht bereits im Pool vorhanden war.
Der Compiler weiß, dass sich die endgültige Variable niemals ändern wird. Wenn wir diese endgültigen Variablen hinzufügen, wird die Ausgabe in den String-Pool verschoben, da sich auch die
str1 + str2
Ausdrucksausgabe niemals ändert. Schließlich ruft der Compiler nach der Ausgabe der beiden oben genannten endgültigen Variablen die Methode inter auf. Rufen Sie im Falle eines nicht endgültigen Variablen-Compilers die interne Methode nicht auf.quelle