217 votes

Comparaison de chaînes de caractères avec == qui sont déclarées finales en Java

J'ai une question simple sur les chaînes de caractères en Java. Le segment de code simple suivant concatène simplement deux chaînes de caractères 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 aussi évidentes (je comprends la différence entre equals() y == ).


Lorsque ces deux chaînes sont déclarées final comme ça,

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, il retourne true . Pourquoi est-ce que final fait une différence ? Cela a-t-il un rapport avec la réserve de stagiaires ou suis-je simplement induit en erreur ?

231voto

Rohit Jain Points 90368

Lorsque vous déclarez un String (qui est immuable ) variable comme final et l'initialiser avec une expression constante du temps de compilation, elle devient également une expression constante du temps de compilation, et sa valeur est inlined par le compilateur où elle est utilisée. Ainsi, dans votre deuxième exemple de code, après l'inlining des valeurs, la concaténation de chaînes de caractères est traduite par le compilateur en :

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

qui, comparé à "string" vous donnera true parce que les chaînes de caractères sont interné .

Desde 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 en temps de compilation (§15.28), est appelée une variable constante .

Également de JLS §15.28 - Expression constante :

Les expressions constantes du temps de compilation de type String sont toujours "interné" de manière à partager des instances uniques, en utilisant la méthode String#intern() .


Ce n'est pas le cas dans votre premier exemple de code, où l'élément String les variables ne sont pas final . Il ne s'agit donc pas d'expressions constantes au moment de la compilation. L'opération de concaténation y sera retardée jusqu'au moment de l'exécution, ce qui conduira à la création d'un nouveau fichier String objet. Vous pouvez le vérifier en comparant le code d'octet des deux codes.

Le premier exemple de code (non final version) est compilé dans 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

Il est clair qu'il stocke str y ing dans deux variables distinctes, et en utilisant StringBuilder pour effectuer l'opération de concaténation.

Alors que, votre deuxième exemple de code ( final version) ressemble à ça :

  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

Donc il met directement en ligne la variable finale pour créer String string au moment de la compilation, qui est chargé par ldc opération en étape 0 . Ensuite, la deuxième chaîne littérale est chargée par ldc opération en étape 7 . Il n'implique pas la création d'un nouveau String au moment de l'exécution. La chaîne est déjà connue au moment de la compilation, et ils sont internés.

31voto

Pradeep Simha Points 4232

D'après mes recherches, tous les final String sont internés en Java. Extrait d'un des articles du blog :

Donc, si vous avez vraiment besoin de comparer deux chaînes 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 comparer des chaînes.

Donc ça veut dire que si vous appelez String.intern() vous pouvez comparer deux chaînes de caractères en utilisant == opérateur. Mais ici String.intern() n'est pas nécessaire car en Java final String sont internés.

Vous pouvez trouver plus d'informations Comparaison de chaînes de caractères à l'aide de l'opérateur == et Javadoc pour String.intern() méthode.

Consultez également cette Stackoverflow pour plus d'informations.

21voto

Pshemo Points 34648

Si vous jetez un coup d'œil à 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 sa décompilation avec javap -c ClassWithTheseMethods 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;
      ...

y

  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 y str2 donc

String concat=str1+str2;

sera compilé en

String concat = new StringBuilder(str1).append(str2).toString();

ce qui signifie que concat seront créées au moment de l'exécution et ne proviendront donc pas du pool String.


De plus, si les chaînes sont finales, le compilateur peut supposer qu'elles ne changeront jamais, donc au lieu d'utiliser StringBuilder il peut concaténer ses valeurs en toute sécurité, de sorte que

String concat = str1 + str2;

peut être changé en

String concat = "str" + "ing";

et concaténés en

String concat = "string";

ce qui signifie que concate deviendra une chaîne littérale qui sera internée dans un pool de chaînes de caractères, puis comparée à la même chaîne littérale de ce pool en if déclaration.

14voto

pnathan Points 486

Concept de pool de conts de pile et de chaîne enter image description here

3voto

Sotirios Delimanolis Points 77933

Voyons un peu de code d'octet pour la fonction final exemple

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
}

Sur 0: y 2: le String "string" est poussé sur la pile (depuis le pool de constantes) et stocké dans la variable locale concat directement. Vous pouvez en déduire que le compilateur crée (concatène) le fichier String "string" au moment de la compilation.

Le non final code d'octet

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
}

Ici, vous avez deux String constantes, "str" y "ing" qui doivent être concaténées au moment de l'exécution avec une StringBuilder .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X