web-dev-qa-db-fra.com

Comparaison de chaînes avec == déclarées finales en Java

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?

214
Tiny

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-à-dire final 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éthode String#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.

229
Rohit Jain

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.

31
Pradeep Simha

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.

21
Pshemo

Concept de pool de piles et de cordes enter image description here

14
pnathan

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.

3

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.

0
Premraj