Confrontando le stringhe con == che sono dichiarate finali in Java


220

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

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

L'espressione di confronto concat=="string"ritorna falseovvia (capisco la differenza tra equals()e ==).


Quando queste due stringhe vengono dichiarate in questo finalmodo,

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, ritorna true. Perché fa la finaldifferenza? Deve fare qualcosa con il pool di stagisti o sono solo stato ingannato?


22
Ho sempre trovato sciocco che uguale a fosse il modo predefinito di verificare la presenza di contenuti uguali, invece di avere == farlo e usare semplicemente referenceEquals o qualcosa di simile per verificare se i puntatori sono uguali.
Davio,

25
Questo non è un duplicato di "Come faccio a confrontare le stringhe in Java?" in ogni modo. L'OP capisce la differenza tra equals()e ==nel contesto delle stringhe, e fa una domanda più significativa.
Arshajii,

@Davio Ma come funzionerebbe quando la lezione non lo è String? Penso che sia molto logico, non sciocco, fare il confronto dei contenuti equals, un metodo che possiamo scavalcare per dire quando consideriamo due oggetti uguali e fare il confronto delle identità ==. Se il confronto dei contenuti fosse fatto da ==noi non potremmo ignorare quello per definire ciò che intendiamo per "contenuti uguali", e avere il significato di equalse ==invertito solo per Strings sarebbe sciocco. Inoltre, a prescindere da ciò, non vedo comunque alcun vantaggio nel ==fare il confronto dei contenuti anziché equals.
SantiBailors

@SantiBailors hai ragione sul fatto che questo è proprio come funziona in Java, ho anche usato C # dove == è sovraccarico per l'uguaglianza dei contenuti. Un ulteriore vantaggio dell'utilizzo di == è che è null-safe: (null == "qualcosa") restituisce false. Se usi uguale a 2 oggetti, devi sapere se uno può essere nullo o rischi di lanciare una NullPointerException.
Davio

Risposte:


232

Quando dichiari una variabile String(che è immutabile ) come finale la inizializzi 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 tuo secondo esempio di codice, dopo aver incorporato i valori, la concatenazione di stringhe viene tradotta dal compilatore in:

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

che se confrontato con "string"ti darà true, perché i valori letterali delle stringhe sono internati .

Da JLS §4.12.4 - finalVariabili :

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

Anche da JLS § 15.28 - Espressione costante:

Costanti in fase di compilazione espressioni del tipo Stringsono sempre "internati" , in modo da condividere le istanze univoche, utilizzando il metodo String#intern().


Questo non è il caso nel tuo primo esempio di codice, dove le Stringvariabili non lo 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 Stringoggetto. È possibile verificarlo confrontando il codice byte di entrambi i codici.

Il primo esempio di codice (non finalversione) viene 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."<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

Chiaramente sta memorizzando stre ingin due variabili separate, e usando StringBuilderper eseguire l'operazione di concatenazione.

Considerando che il tuo secondo esempio di codice ( finalversione) è simile al seguente:

  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 incorpora direttamente la variabile finale per creare String stringal momento della compilazione, che viene caricata ldcdall'operazione nel passaggio 0. Quindi la seconda stringa letterale viene caricata ldcdall'operazione nel passaggio 7. Non comporta la creazione di alcun nuovo Stringoggetto in fase di esecuzione. Le String sono già note al momento della compilazione e vengono internate.


2
Non c'è nulla che impedisca ad altre implementazioni del compilatore Java di non internare un'ultima stringa, giusto?
Alvin,

13
@Alvin il JLS richiede che le espressioni di stringa costanti in fase di compilazione siano internate. Qualsiasi implementazione conforme deve fare la stessa cosa qui.
Tavian Barnes,

Al contrario, il JLS impone che un compilatore non debba ottimizzare la concatenazione nella prima versione non definitiva ? Al compilatore è vietato produrre codice su cui valutare il confronto true?
phant0m

1
@ phant0m prendendo l'attuale formulazione della specifica , “ L' Stringoggetto è stato appena creato (§12.5) a meno che l'espressione non sia un'espressione costante (§15.28). "Letteralmente, l'applicazione di un'ottimizzazione nella versione non definitiva non è consentita, poiché una stringa" appena creata "deve avere un'identità oggetto diversa. Non so se questo è intenzionale. Dopotutto, l'attuale strategia di compilazione è quella di delegare a una funzione di runtime che non documenta tali restrizioni.
Holger,

31

Secondo la mia ricerca, tutti final Stringsono internati 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.

Quindi significa che se chiami String.intern()puoi confrontare due stringhe usando l' ==operatore. Ma qui String.intern()non è necessario perché in Java final Stringsono internati internamente.

È possibile trovare ulteriori informazioni Confronto delle stringhe utilizzando l'operatore == e il metodo Javadoc per String.intern () .

Consultare anche questo post Stackoverflow per ulteriori informazioni.


3
Le stringhe intern () non vengono raccolte in modo inutile ed è memorizzata nello spazio permgen che è basso, quindi potresti trovarti nei guai come errore di memoria insufficiente se non usato correttamente.
Ajeesh,

@Ajeesh - Le stringhe internate possono essere raccolte. Anche le stringhe internate risultanti da espressioni costanti possono essere raccolte in alcune circostanze.
Stephen C,

21

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

ed è decompilato con le javap -c ClassWithTheseMethods versioni 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."<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;
      ...

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 StringBuilderper concatenare str1e str2così

String concat=str1+str2;

sarà compilato in

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

ciò significa che concatverrà creato in fase di esecuzione in modo che non provenga dal pool di stringhe.


Anche se le stringhe sono definitive, il compilatore può presumere che non cambieranno mai, quindi invece di usarlo StringBuilderpuò concatenare i suoi valori in modo sicuro

String concat = str1 + str2;

può essere modificato in

String concat = "str" + "ing";

e concatenato in

String concat = "string";

il che significa che concatediventerà letterale pungiglione che verrà internato nel pool di stringhe e quindi confrontato con lo stesso valore letterale di stringa di quel pool ifnell'istruzione.


15

Concetto dello stagno dei coni dello stack e della stringa inserisci qui la descrizione dell'immagine


6
Che cosa? Non capisco come questo abbia voti positivi. Puoi chiarire la tua risposta?
Cᴏʀʏ

Penso che la risposta prevista sia che, poiché str1 + str2 non è ottimizzato per una stringa internata, il confronto con una stringa del pool di stringhe porterà a una condizione falsa.
viki.omega9,

3

Vediamo un po 'di codice byte per l' finalesempio

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:e 2:, il String "string"viene inserito nello stack (dal pool costante) e memorizzato concatdirettamente nella variabile locale . Puoi dedurre che il compilatore sta creando (concatenando) lo String "string"stesso al momento della compilazione.

Il finalcodice non byte

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
}

Qui ci sono due Stringcostanti "str"e "ing"che devono essere concatenate in fase di esecuzione con a StringBuilder.


0

Tuttavia, quando si crea utilizzando la notazione letterale String di Java, chiama automaticamente il metodo intern () per inserire quell'oggetto nel pool String, a condizione che non sia già presente nel pool.

Perché final fa la differenza?

Il compilatore sa che la variabile finale non cambierà mai, quando aggiungiamo queste variabili finali l'output va al pool di stringhe perché l' str1 + str2output delle espressioni non cambierà mai, quindi alla fine il compilatore chiama il metodo inter dopo l'output delle due variabili finali sopra. In caso di compilatore di variabili non definitivo non chiamare il metodo intern.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.