J'ai une simple question à propos des chaînes en Java. Le segment de code simple suivant concatène simplement deux chaînes et les compare ensuite avec ==
.
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
L'expression de comparaison concat=="string"
renvoie false
comme une évidence (je comprends la différence entre equals()
et ==
).
Lorsque ces deux chaînes sont déclarées final
comme tel,
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
L'expression de comparaison concat=="string"
, dans ce cas, renvoie true
. Pourquoi final
fait-il une différence? Est-ce que cela doit faire quelque chose avec le pool interne ou je suis simplement induit en erreur?
Lorsque vous déclarez une variable String
(qui est immuable} _ sous la forme final
et l'initialisez avec une expression constante à la compilation, elle devient également une expression constante à la compilation et sa valeur est insérée par le compilateur. c'est utilisé. Ainsi, dans votre deuxième exemple de code, après avoir aligné les valeurs, la concaténation de chaînes est traduite par le compilateur en:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
ce qui, comparé à "string"
, vous donnera true
, car les littéraux de chaîne sont interné _.
De JLS §4.12.4 - final
Variables :
Une variable de type primitif ou de type
String
, c'est-à-direfinal
et initialisée avec une expression constante au moment de la compilation (§15.28), est appelée une variable constante.
Aussi de JLS §15.28 - Expression constante:
Les expressions constantes au moment de la compilation de type
String
sont toujours "interned" afin de partager des instances uniques, en utilisant la méthodeString#intern()
.
Ce n'est pas le cas dans votre premier exemple de code, où les variables String
ne sont pas final
. Donc, ce ne sont pas des expressions constantes au moment de la compilation. L'opération de concaténation sera retardée jusqu'à l'exécution, ce qui conduira à la création d'un nouvel objet String
. Vous pouvez le vérifier en comparant le code octet des deux codes.
Le premier exemple de code (version non -final
) est compilé avec le code d'octet suivant:
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
Clairement, il stocke str
et ing
dans deux variables distinctes et utilise StringBuilder
pour effectuer l'opération de concaténation.
Alors que votre deuxième exemple de code (final
version) ressemble à ceci:
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
Elle insère donc directement la variable finale pour créer String string
au moment de la compilation, qui est chargé par l'opération ldc
à l'étape 0
. Ensuite, le deuxième littéral de chaîne est chargé par l'opération ldc
à l'étape 7
. Cela n'implique pas la création d'un nouvel objet String
au moment de l'exécution. La chaîne est déjà connue au moment de la compilation et ils sont internés.
Selon mes recherches, tous les final String
sont internés en Java. De l'un des post de blog:
Donc, si vous avez vraiment besoin de comparer deux String en utilisant == ou! =, Assurez-vous d'appeler la méthode String.intern () avant de faire la comparaison. Sinon, préférez toujours String.equals (String) pour la comparaison de chaînes.
Cela signifie donc que si vous appelez String.intern()
, vous pouvez comparer deux chaînes à l'aide de l'opérateur ==
. Mais ici, String.intern()
n'est pas nécessaire car, en Java, final String
est interné en interne.
Vous pouvez trouver plus d'informations Comparaison de chaînes en utilisant l'opérateur == et Javadoc pour la méthode String.intern () .
Reportez-vous également à ce message Stackoverflow pour plus d'informations.
Si vous jetez un oeil à ces méthodes
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");
}
et ses versions décompilées avec javap -c ClassWithTheseMethods
que vous verrez
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;
...
et
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
...
Donc si les chaînes ne sont pas finales, le compilateur devra utiliser StringBuilder
pour concaténer str1
et str2
donc
String concat=str1+str2;
sera compilé pour
String concat = new StringBuilder(str1).append(str2).toString();
ce qui signifie que concat
sera créé à l'exécution et ne viendra donc pas du pool String.
De plus, si les chaînes sont finales, le compilateur peut supposer qu'elles ne changeront jamais. Au lieu d'utiliser StringBuilder
, il peut concaténer en toute sécurité ses valeurs afin
String concat = str1 + str2;
peut être changé en
String concat = "str" + "ing";
et concaténé dans
String concat = "string";
ce qui signifie que concate
deviendra littéral sting qui sera interné dans le pool de chaînes de caractères et comparé au même littéral de chaîne de ce pool dans l'instruction if
.
Concept de pool de piles et de cordes
Voyons du code octet pour l'exemple 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
}
En 0:
et 2:
, la String
"string"
est poussée dans la pile (à partir du pool constant) et stockée directement dans la variable locale concat
. Vous pouvez en déduire que le compilateur est en train de créer (concaténer) la String
"string"
elle-même au moment de la compilation.
Le code d'octet non final
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
}
Vous avez ici deux constantes String
, "str"
et "ing"
, qui doivent être concaténées à l'exécution avec un StringBuilder
.
Cependant, lorsque vous créez en utilisant la notation littérale String de Java, la méthode intern () est automatiquement appelée pour placer cet objet dans le pool String, à condition qu'il ne soit pas déjà présent dans le pool.
Pourquoi la finale fait-elle la différence?
Compilateur sait que la variable finale ne changera jamais. Lorsque nous ajoutons ces variables finales, la sortie passe au pool de chaînes en raison de la sortie de l'expression str1 + str2
. Elle ne change jamais non plus. Dans le cas d'un compilateur de variable non finale, n'appelez pas la méthode intern.