una parte manejo extraer diferente declarar concatenar comparar caracteres cadenas cadena java string final

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 es final 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étodo String#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 .