sujeto resueltos predicado modificadores modificador indirecto ejercicios ejemplos directo java static modifier

java - resueltos - Diferencia entre modificador estático y bloque estático



modificadores del sujeto y predicado ejercicios resueltos (7)

¿Alguien me explica las diferencias entre las siguientes dos afirmaciones?

Una variable static final inicializada por un bloque de código static :

private static final String foo; static { foo = "foo"; }

Una variable static final inicializada por una asignación:

private static final String foo = "foo";


El JLS describe algunos comportamientos especiales de lo que llama variables constantes , que son variables final (ya sea static o no) que se inicializan con expresiones constantes de tipo String o primitivo.

Las variables constantes tienen una gran diferencia con respecto a la compatibilidad binaria: los valores de las variables constantes se convierten en parte de la API de la clase, en lo que respecta al compilador.

Un ejemplo:

class X { public static final String XFOO = "xfoo"; } class Y { public static final String YFOO; static { YFOO = "yfoo"; } } class Z { public static void main(String[] args) { System.out.println(X.XFOO); System.out.println(Y.YFOO); } }

Aquí, XFOO es una "variable constante" y YFOO no lo es, pero son equivalentes. La clase Z imprime cada uno de ellos. Compile esas clases, luego javap -v XYZ con javap -v XYZ , y aquí está el resultado:

Clase X:

Constant pool: #1 = Methodref #3.#11 // java/lang/Object."<init>":()V #2 = Class #12 // X #3 = Class #13 // java/lang/Object #4 = Utf8 XFOO #5 = Utf8 Ljava/lang/String; #6 = Utf8 ConstantValue #7 = String #14 // xfoo #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = NameAndType #8:#9 // "<init>":()V #12 = Utf8 X #13 = Utf8 java/lang/Object #14 = Utf8 xfoo { public static final java.lang.String XFOO; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: String xfoo X(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return }

Clase Y:

Constant pool: #1 = Methodref #5.#12 // java/lang/Object."<init>":()V #2 = String #13 // yfoo #3 = Fieldref #4.#14 // Y.YFOO:Ljava/lang/String; #4 = Class #15 // Y #5 = Class #16 // java/lang/Object #6 = Utf8 YFOO #7 = Utf8 Ljava/lang/String; #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 <clinit> #12 = NameAndType #8:#9 // "<init>":()V #13 = Utf8 yfoo #14 = NameAndType #6:#7 // YFOO:Ljava/lang/String; #15 = Utf8 Y #16 = Utf8 java/lang/Object { public static final java.lang.String YFOO; descriptor: Ljava/lang/String; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL Y(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #2 // String yfoo 2: putstatic #3 // Field YFOO:Ljava/lang/String; 5: return }

Clase Z:

Constant pool: #1 = Methodref #8.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Class #17 // X #4 = String #18 // xfoo #5 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Fieldref #21.#22 // Y.YFOO:Ljava/lang/String; #7 = Class #23 // Z #8 = Class #24 // java/lang/Object #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = NameAndType #9:#10 // "<init>":()V #15 = Class #25 // java/lang/System #16 = NameAndType #26:#27 // out:Ljava/io/PrintStream; #17 = Utf8 X #18 = Utf8 xfoo #19 = Class #28 // java/io/PrintStream #20 = NameAndType #29:#30 // println:(Ljava/lang/String;)V #21 = Class #31 // Y #22 = NameAndType #32:#33 // YFOO:Ljava/lang/String; #23 = Utf8 Z #24 = Utf8 java/lang/Object #25 = Utf8 java/lang/System #26 = Utf8 out #27 = Utf8 Ljava/io/PrintStream; #28 = Utf8 java/io/PrintStream #29 = Utf8 println #30 = Utf8 (Ljava/lang/String;)V #31 = Utf8 Y #32 = Utf8 YFOO #33 = Utf8 Ljava/lang/String; { Z(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String xfoo 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 11: getstatic #6 // Field Y.YFOO:Ljava/lang/String; 14: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 17: return }

Aspectos a tener en cuenta en el desmontaje, que le indican que las diferencias entre X e Y son más profundas que el azúcar sintáctico:

  • XFOO tiene un atributo ConstantValue , lo que significa que su valor es una constante de tiempo de compilación. Mientras que YFOO no lo hace, y usa un bloque static con una instrucción putstatic para inicializar el valor en tiempo de ejecución.

  • La constante de String "xfoo" ha convertido en parte del grupo de constantes de la clase Z , pero "yfoo" no.

  • Z.main usa la ldc (carga constante) para cargar "xfoo" en la pila directamente desde su propio grupo constante, pero usa una instrucción getstatic para cargar el valor de Y.YFOO .

Otras diferencias que encontrarás:

  • Si cambia el valor de XFOO y recompila X.java pero no Z.java , tiene un problema: la clase Z todavía está usando el valor anterior. Si cambia el valor de YFOO y recompila Y.java , la clase Z usa el nuevo valor tanto si recompila Z.java como si no.

  • Si elimina el archivo X.class completo, la clase Z aún se ejecuta correctamente. Z no tiene dependencia de tiempo de ejecución en X Mientras que si elimina el archivo Y.class , la clase Z no puede inicializarse con una ClassNotFoundException: Y

  • Si genera documentación para las clases con javadoc, la página "Valores de campo constante" documentará el valor de XFOO , pero no el valor de YFOO .

El JLS describe los efectos anteriores que las variables constantes tienen en los archivos de clase compilados en §13.1.3 :

Una referencia a un campo que es una variable constante (§4.12.4) debe resolverse en tiempo de compilación al valor V denotado por el inicializador de la variable constante.

Si dicho campo es static , entonces no debe haber ninguna referencia al campo en el código en un archivo binario, incluida la clase o interfaz que declaró el campo. Tal campo siempre debe parecer inicializado (§12.4.2); el valor inicial predeterminado para el campo (si es diferente a V) nunca debe observarse.

Si dicho campo no es static , entonces no debe haber ninguna referencia al campo en el código en un archivo binario, excepto en la clase que contiene el campo. (Será una clase en lugar de una interfaz, ya que una interfaz solo tiene campos static ). La clase debe tener un código para establecer el valor del campo en V durante la creación de la instancia (§12.5).

Y en §13.4.9 :

Si un campo es una variable constante (§4.12.4), y además es static , eliminar la palabra clave final o cambiar su valor no interrumpirá la compatibilidad con los binarios preexistentes al hacer que no se ejecuten, pero no verán ninguno nuevo valor para un uso del campo a menos que se vuelvan a compilar.

[...]

La mejor manera de evitar problemas con las "constantes inconstantes" en el código ampliamente distribuido es usar variables constantes static solo para valores que realmente es poco probable que cambien. Aparte de las constantes matemáticas verdaderas, recomendamos que el código fuente use muy poco las variables constantes static .

El resultado es que si su biblioteca pública expone cualquier variable constante, nunca debe cambiar sus valores si se supone que su nueva versión de la biblioteca es compatible con el código compilado contra las versiones anteriores de la biblioteca. No necesariamente causará un error, pero el código existente probablemente funcionará mal ya que tendrá ideas obsoletas sobre los valores de las constantes. (Si su nueva versión de biblioteca necesita que las clases que la usan se vuelvan a compilar de todos modos, cambiar las constantes no causa este problema).

Por lo tanto, inicializar una constante con un bloque le da más libertad para cambiar su valor, ya que evita que el compilador incruste el valor en otras clases.


El bloqueo estático le brinda más que una simple declaración. En este caso particular es lo mismo. La sección estática se ejecutará en el momento de carga de la clase, antes de que se construya cualquier instancia. Puede llamar a los métodos aquí y asignar sus resultados a campos estáticos. Y puede detectar excepciones en bloques estáticos.


En el segundo caso, el valor de foo es un enlace temprano, es decir, el compilador identifica y asigna el valor de foo a la variable FOO , que no se puede cambiar, y esto estará disponible aparte con el código de bytes .

private static final String FOO = "foo";

y en el primer caso, el valor de foo se inicializa justo después de cargar la clase como una primera asignación antes de la variable de instancia asignada, también aquí puede capturar excepciones o se puede asignar un campo estático llamando a métodos estáticos en bloque estático.

private static final String FOO; static { FOO ="foo";}

Entonces, siempre que llegue una condición en la que el compilador deba identificar el valor de la variable foo, la condición II funcionará, por ejemplo, el valor del caso: en casos de cambio .


En este ejemplo, hay una sutil diferencia: en su primer ejemplo, foo no está determinado a ser una constante de tiempo de compilación, por lo que no se puede usar como un caso en bloques de switch (y no se incluiría en otro código ); en su segundo ejemplo, es. Así por ejemplo:

switch (args[0]) { case foo: System.out.println("Yes"); break; }

Eso es válido cuando foo se considera una expresión constante, pero no cuando es "solo" una variable final estática.

Sin embargo, los bloques de inicializador estático generalmente se usan cuando tiene un código de inicialización más complicado, como llenar una colección.

El momento para la inicialización se describe en JLS 12.4.2 ; los campos finales estáticos que se consideran constantes de tiempo de compilación se inicializan primero (paso 6) y los inicializadores se ejecutan después (paso 9); Todos los inicializadores (ya sean inicializadores de campo o inicializadores estáticos) se ejecutan en orden de texto.


La única diferencia es el tiempo de inicialización.

Java primero inicializa los miembros y luego los bloques estáticos.


Un aspecto adicional: considere el caso cuando tiene múltiples campos estáticos, y sí, este es un caso de esquina ...

Como se indica en la respuesta de Jon Skeet, el JLS define el orden exacto de inicialización. Sin embargo, si por alguna razón tiene que inicializar múltiples atributos estáticos en un orden específico, puede hacer que la secuencia de inicialización sea claramente visible en el código. Cuando se utiliza la inicialización de campo directa: algunos formateadores de código (y desarrolladores) pueden decidir en algún momento ordenar los campos de manera diferente, esto afectará directamente la forma en que los campos se inicializan e introducen efectos no deseados.

Por cierto, si desea seguir las convenciones comunes de codificación de Java, debe usar letras mayúsculas al definir ''constantes'' (campos estáticos finales).

--- editado reflejando los comentarios de Jon Skeet ---


private static final String foo; static { foo ="foo";}

El valor de foo se inicializa cuando se carga la clase y se ejecutan los inicializadores estáticos .

private static final String foo = "foo";

Aquí, el valor de foo será una constante de tiempo de compilación . Entonces, en realidad, "foo" estará disponible como parte del propio código de bytes .