Confronto tra stringhe con == che sono dichiarate definitive in Java

Ho una semplice domanda sulle stringhe in Java. Il seguente segmento di codice semplice concatena solo due stringhe e quindi le confronta con == .

 String str1="str"; String str2="ing"; String concat=str1+str2; System.out.println(concat=="string"); 

L’espressione di confronto concat=="string" restituisce false come ovvio (capisco la differenza tra equals() e == ).


Quando queste due stringhe sono dichiarate final modo,

 final String str1="str"; final String str2="ing"; String concat=str1+str2; System.out.println(concat=="string"); 

L’espressione di confronto concat=="string" , in questo caso restituisce true . Perché la final fa la differenza? Deve fare qualcosa con la piscina interna o mi sto semplicemente ingannando?

Quando si dichiara una variabile String (che è immutabile ) come final e la si inizializza con un’espressione costante in fase di compilazione, diventa anche un’espressione costante in fase di compilazione e il suo valore viene sottolineato dal compilatore in cui viene utilizzato. Quindi, nel secondo esempio di codice, dopo aver integrato i valori, la concatenazione di stringhe viene tradotta dal compilatore in:

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

che rispetto alla "string" ti darà true , perché i letterali delle stringhe sono internati .

Da JLS §4.12.4 – Variabili final :

Una variabile di tipo primitivo o tipo String , che è final e inizializzata con un’espressione costante in fase di compilazione (§15.28), è chiamata variabile costante .

Anche da JLS §15.28 – Espressione costante:

Le espressioni costanti in fase di compilazione di tipo String sono sempre “internate” in modo da condividere istanze univoche, usando il metodo String#intern() .


Questo non è il caso nel tuo primo esempio di codice, in cui le variabili String non sono final . Quindi, non sono espressioni costanti in fase di compilazione. L’operazione di concatenazione verrà ritardata fino al runtime, portando così alla creazione di un nuovo object String . È ansible verificare questo confrontando il codice byte di entrambi i codici.

Il primo esempio di codice (versione non final ) è compilato nel seguente codice byte:

  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."":()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 

Chiaramente sta memorizzando str and ing in due variabili separate e usando StringBuilder per eseguire l’operazione di concatenazione.

Considerando che, il tuo secondo esempio di codice (versione final ) assomiglia a questo:

  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 

Quindi allinea direttamente la variabile finale per creare una string String in fase di compilazione, che viene caricata dall’operazione ldc nel passaggio 0 . Quindi la seconda stringa letterale viene caricata dall’operazione ldc nel passaggio 7 . Non implica la creazione di alcun nuovo object String in fase di runtime. La stringa è già nota al momento della compilazione e sono internati.

Secondo la mia ricerca, tutte le final String sono internate in Java. Da uno dei post del blog:

Quindi, se hai davvero bisogno di confrontare due string usando == o! = Assicurati di chiamare il metodo String.intern () prima di fare il confronto. Altrimenti, preferisci sempre String.equals (String) per il confronto String.

Significa che se si chiama String.intern() è ansible confrontare due stringhe usando l’operatore == . Ma qui String.intern() non è necessario perché nella final String Java sono internamente internati.

Puoi trovare ulteriori informazioni sulla comparazione delle stringhe usando l’operatore == e il metodo Javadoc per String.intern () .

Riferirsi anche a questo post StackOverflow per maggiori informazioni.

Se dai un’occhiata a questi metodi

 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"); } 

e la sua decompilata con le versioni di javap -c ClassWithTheseMethods che vedrai

  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."":(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; ... 

e

  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 ... 

Quindi se le stringhe non sono il compilatore finale dovrà usare StringBuilder per concatenare str1 e str2 così

 String concat=str1+str2; 

sarà compilato per

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

il che significa che concat verrà creato in fase di runtime e quindi non proviene dal pool di stringhe.


Anche se le stringhe sono definitive, il compilatore può supporre che non cambieranno mai così invece di usare StringBuilder può tranquillamente concatenare i suoi valori così

 String concat = str1 + str2; 

può essere cambiato in

 String concat = "str" + "ing"; 

e concatenato in

 String concat = "string"; 

il che significa che concate diventerà letterale di puntura che verrà internato in un pool di stringhe e quindi confrontato con lo stesso letterale stringa da quel pool in istruzione if .

Concetto di pool di stack e string concept inserisci la descrizione dell'immagine qui

Vediamo un codice byte per l’ final esempio

 Compiled from "Main.java" public class Main { public Main(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()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 } 

A 0: e 2: String la String "string" viene inserita nello stack (dal pool costante) e memorizzata direttamente nella variabile locale concat . È ansible dedurre che il compilatore sta creando (concatenando) la "string" stessa al momento della compilazione.

Il codice byte non final

 Compiled from "Main2.java" public class Main2 { public Main2(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()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."":()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 } 

Qui ci sono due costanti String , "str" e "ing" che devono essere concatenate in runtime con un StringBuilder .

Tuttavia, quando si crea usando la notazione letterale String di Java, si chiama automaticamente il metodo intern () per mettere quell’object nel pool String, a condizione che non sia già presente nel pool.

Perché la finale fa la differenza?

Il compilatore sa che la variabile finale non cambierà mai, quando aggiungiamo queste variabili finali l’output va a String Pool a causa str1 + str2 espressioni str1 + str2 che non cambierà mai, quindi finalmente il richiamo delle chiamate inter metodo dopo l’output delle due variabili finali sopra. In caso di compilatore variabile non finale non chiamare il metodo interno.