parte - Comparando cadenas con== que se declaran definitivas en Java
extraer una parte de una cadena en java (6)
Tengo una pregunta simple sobre cadenas en Java. El siguiente segmento de código simple solo concatena dos cadenas y luego las compara con ==
.
String str1="str";
String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
La expresión de comparación concat=="string"
devuelve false
como obvio (entiendo la diferencia entre equals()
y ==
).
Cuando estas dos cadenas se declaran final
como tal,
final String str1="str";
final String str2="ing";
String concat=str1+str2;
System.out.println(concat=="string");
La expresión de comparación concat=="string"
, en este caso devuelve true
. ¿Por qué la final
hace una diferencia? ¿Tiene que hacer algo con el grupo de internos o simplemente estoy siendo engañado?
Concepto de grupo de pila y cadena de conts
Cuando declara una variable String
(que es inmutable ) como final
, y la inicializa con una expresión de constante de compilación, también se convierte en una expresión de constante de compilación y su valor es alineado por el compilador donde se usa. Entonces, en su segundo ejemplo de código, después de ingresar los valores, la concatenación de cadenas es traducida por el compilador a:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
que en comparación con "string"
le dará true
, porque los literales de cadena están internados .
De JLS §4.12.4 - Variables final
:
Una variable de tipo primitivo o tipo
String
, que esfinal
e inicializa con una expresión de constante de compilación (§15.28), se llama variable constante .
También de JLS §15.28 - Expresión constante:
Las expresiones constantes en tiempo de compilación de tipo
String
siempre se "internan" para compartir instancias únicas, utilizando el métodoString#intern()
.
Este no es el caso en su primer ejemplo de código, donde las variables de String
no son final
. Por lo tanto, no son expresiones constantes en tiempo de compilación. La operación de concatenación se retrasará hasta el tiempo de ejecución, lo que llevará a la creación de un nuevo objeto String
. Puede verificar esto comparando el código de byte de ambos códigos.
El primer ejemplo de código (versión no final
) se compila al siguiente código de 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
Claramente, está almacenando str
en dos variables separadas y utilizando StringBuilder
para realizar la operación de concatenación.
Considerando que, su segundo ejemplo de código (versión final
) se ve así:
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
Por lo tanto, alinea directamente la variable final para crear una string
en tiempo de compilación, que se carga mediante la operación ldc
en el paso 0
. Luego, el segundo literal de cadena se carga mediante la operación ldc
en el paso 7
. No implica la creación de ningún nuevo objeto String
en tiempo de ejecución. La cadena ya se conoce en tiempo de compilación y están internados.
Según mi investigación, todas las final String
están internadas en Java. De una de las entradas del blog:
Por lo tanto, si realmente necesita comparar dos cadenas usando == o! = Asegúrese de llamar al método String.intern () antes de hacer una comparación. De lo contrario, siempre prefiera String.equals (String) para la comparación de cadenas.
Entonces significa que si llama a String.intern()
puede comparar dos cadenas usando el operador ==
. Pero aquí String.intern()
no es necesario porque en Java final String
se internan internamente.
Puede encontrar más información Comparación de cadenas con el operador == y Javadoc para el método String#intern() .
Consulte también esta publicación de para obtener más información.
Si echas un vistazo a estos métodos.
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");
}
y se descompilará con las versiones javap -c ClassWithTheseMethods
que verá
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
...
Entonces, si las cadenas no son definitivas, el compilador tendrá que usar StringBuilder
para concatenar str1
y str2
por lo que
String concat=str1+str2;
será compilado para
String concat = new StringBuilder(str1).append(str2).toString();
lo que significa que el concat
se creará en tiempo de ejecución, por lo que no provendrá de String pool.
Además, si las cadenas son definitivas, el compilador puede asumir que nunca cambiarán, por lo que en lugar de usar StringBuilder
puede concatenar sus valores de manera segura
String concat = str1 + str2;
se puede cambiar a
String concat = "str" + "ing";
y concatenado en
String concat = "string";
lo que significa que concate
se convertirá en literal de cadena, que se internará en el conjunto de cadenas y luego se comparará con el mismo literal de cadena de ese grupo en la declaración if
.
Sin embargo, cuando crea utilizando la notación literal de String de Java, se llama automáticamente al método intern () para colocar ese objeto en el conjunto de String, siempre que no esté ya presente en el pool.
¿Por qué la final hace una diferencia?
El compilador sabe que la variable final nunca cambiará, cuando agregamos estas variables finales, la salida va a String Pool porque la expresión str1 + str2
salida tampoco cambiará, por lo que finalmente el compilador llama al método inter después de la salida de las dos variables finales anteriores. En el caso de compilador de variable no final no llamar al método interno.
Veamos algunos códigos de bytes para el ejemplo 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:
y 2:
la String
"string"
se empuja en la pila (desde el conjunto constante) y se almacena directamente en la variable local concat
. Puede deducir que el compilador está creando (concatenando) la propia "string"
en el momento de la compilación.
El código de byte no 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
}
Aquí tiene dos constantes de String
, "str"
y "ing"
que deben ser concatenadas en tiempo de ejecución con un StringBuilder
.