Porównywanie łańcuchów z==, które są deklarowane jako końcowe w Javie
Mam proste pytanie o stringi w Javie. Poniższy segment prostego kodu łączy dwa ciągi znaków, a następnie porównuje je z ==
.
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
Wyrażenie porównawcze concat=="string"
zwraca false
jako oczywiste(Rozumiem różnicę między equals()
i ==
).
Gdy te dwa łańcuchy są zadeklarowane final
w ten sposób,
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
Wyrażenie porównawcze concat=="string"
, w tym przypadku zwraca true
. Dlaczego final
robi różnicę? Czy to musi zrobić coś z pulą stażystów, czy jestem po prostu wprowadzany w błąd?
6 answers
Kiedy zadeklarujesz zmienną String
(która jest niezmienna) jako final
i zainicjalizujesz ją wyrażeniem stałej czasu kompilacji, staje się ona również wyrażeniem stałej czasu kompilacji, a jej wartość jest inlinowana przez kompilator, w którym jest używana. Tak więc, w drugim przykładzie kodu, po wpisaniu wartości, konkatenacja łańcucha jest tłumaczona przez kompilator na:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
Które w porównaniu do "string"
da ci true
, ponieważ literały ciągu są internowany.
Z JLS §4.12.4 - final
zmienne :
Zmienna typu prymitywnego lub typu
String
, czyli {[4] } i zainicjalizowana wyrażeniem stałej czasu kompilacji (§15.28), nazywana jest zmienną stałą.
Również z JLS §15.28-wyrażenie stałe:
Wyrażenia stałe czasu kompilacji typu
String
są zawsze "internowane" tak, aby współdzielić unikalne instancje, używając metodaString#intern()
.
Tak nie jest w pierwszym przykładzie kodu, gdzie zmienne String
nie są final
. Nie są więc wyrażeniami stałymi w czasie kompilacji. Operacja konkatenacji będzie opóźniona do czasu uruchomienia, co doprowadzi do wytworzenia nowego obiektu String
. Można to sprawdzić porównując kod bajtowy obu kodów.
Pierwszy przykład kodu (wersja nie-final
) jest kompilowany do następującego bajtu kod:
Code:
0: ldc #2; //String str
2: astore_1
3: ldc #3; //String ing
5: astore_2
6: new #4; //class java/lang/StringBuilder
9: dup
10: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9; //String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10; //Method java/io/PrintStream.println:(Z)V
42: return
Najwyraźniej przechowuje str
i ing
w dwóch oddzielnych zmiennych i używa StringBuilder
do wykonania operacji konkatenacji.
Natomiast drugi przykład kodu (final
wersja) wygląda tak:
Code:
0: ldc #2; //String string
2: astore_3
3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2; //String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
20: return
Więc bezpośrednio w linii końcowej zmiennej utworzyć łańcuch string
w czasie kompilacji, który jest ładowany przez ldc
operacji w kroku 0
. Następnie drugi literał jest ładowany przez operację ldc
w kroku 7
. Nie obejmuje tworzenie dowolnego nowego obiektu String
w czasie wykonywania. Łańcuch jest już znany w czasie kompilacji i są one internowane.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-10 04:58:14
Z moich badań wynika, że wszyscy final String
są internowani w Javie. Z jednego z wpisów na blogu:
Więc, jeśli naprawdę potrzebujesz porównać dwa ciąg znaków za pomocą = = or != upewnij się, że wywołujesz String.metoda intern () przed dokonaniem porównania. W przeciwnym razie zawsze preferuj ciąg.equals (String) dla porównania łańcuchów.
Oznacza to, że wywołując String.intern()
można porównać dwa ciągi znaków za pomocą operatora ==
. Ale tutaj String.intern()
nie jest konieczne, ponieważ w Javie final String
są wewnętrznie internowane.
Ty można znaleźć więcej informacji porównywanie łańcuchów za pomocą = = operator i Javadoc dla łańcuchów.intern () metoda.
Aby uzyskać więcej informacji, zapoznaj się również z tym postem Stoskoverflow .Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:20
Jeśli przyjrzysz się tej metodzie
public void noFinal() {
String str1 = "str";
String str2 = "ing";
String concat = str1 + str2;
System.out.println(concat == "string");
}
public void withFinal() {
final String str1 = "str";
final String str2 = "ing";
String concat = str1 + str2;
System.out.println(concat == "string");
}
I jego dekompilacja z javap -c ClassWithTheseMethods
wersje, które zobaczysz
public void noFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: new #19 // class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: invokevirtual #30 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
...
I
public void withFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: ldc #44 // String string
8: astore_3
...
Więc jeśli ciągi nie są końcowymi kompilatorami będzie musiał użyć {[9] } do konkatenacji str1
i str2
więc
String concat=str1+str2;
Zostanie skompilowana do
String concat = new StringBuilder(str1).append(str2).toString();
Co oznacza, że concat
zostanie utworzony w czasie wykonywania, Więc nie będzie pochodzić z puli łańcuchów.
Również jeśli łańcuchy są ostateczne, kompilator może założyć, że nigdy Zmień tak, aby zamiast używać StringBuilder
mógł bezpiecznie łączyć swoje wartości tak
String concat = str1 + str2;
Można zmienić na
String concat = "str" + "ing";
I konkatenowany na
String concat = "string";
Co oznacza, że concate
stanie się literał Stinga, który zostanie internowany w Puli łańcuchów, a następnie porównany z tym samym literałem łańcuchowym z tej puli w instrukcji if
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-09 15:31:18
Stack and string conts pool concept
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 06:33:14
Zobaczmy kod bajtowy dla przykładu final
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: ldc #2 // String string
2: astore_3
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2 // String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
20: return
}
At 0:
and 2:
, the String
"string"
jest wepchnięty na stos (ze stałej Puli) i przechowywany bezpośrednio w zmiennej lokalnej concat
. Można wywnioskować, że kompilator tworzy (konkatenuje) String
"string"
się w czasie kompilacji.
Kod nie final
bajtowy
Compiled from "Main2.java"
public class Main2 {
public Main2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: ldc #2 // String str
2: astore_1
3: ldc #3 // String ing
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri
ngBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9 // String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
42: return
}
Tutaj masz dwie stałe String
, "str"
i "ing"
, które muszą być połączone w czasie wykonywania z StringBuilder
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 05:09:01
Jednak, gdy tworzysz w Javie dosłowną notację łańcuchową, automatycznie wywołuje ona metodę intern (), aby umieścić ten obiekt w Puli łańcuchów, pod warunkiem, że nie był on obecny w Puli.
Dlaczego final robi różnicę?
Kompilator wie, że ostateczna zmienna nigdy się nie zmieni, kiedy dodamy te końcowe zmienne, wyjście przechodzi do puli łańcuchów, ponieważ str1 + str2
wyjście wyrażenia również nigdy się nie zmieni, więc ostatecznie kompilator wywołuje metodę inter po wyjściu powyższych dwóch zmiennych końcowych. W przypadku kompilatora niekończących się zmiennych nie należy wywoływać metody intern.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-11 07:21:40