java - resueltos - Diferencia entre modificador estático y bloque estático
modificadores del sujeto y predicado ejercicios resueltos (7)
Esta pregunta ya tiene una respuesta aquí:
¿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 atributoConstantValue
, lo que significa que su valor es una constante de tiempo de compilación. Mientras queYFOO
no lo hace, y usa un bloquestatic
con una instrucciónputstatic
para inicializar el valor en tiempo de ejecución. -
La constante de
String
"xfoo"
ha convertido en parte del grupo de constantes de la claseZ
, pero"yfoo"
no. -
Z.main
usa laldc
(carga constante) para cargar"xfoo"
en la pila directamente desde su propio grupo constante, pero usa una instruccióngetstatic
para cargar el valor deY.YFOO
.
Otras diferencias que encontrarás:
-
Si cambia el valor de
XFOO
y recompilaX.java
pero noZ.java
, tiene un problema: la claseZ
todavía está usando el valor anterior. Si cambia el valor deYFOO
y recompilaY.java
, la claseZ
usa el nuevo valor tanto si recompilaZ.java
como si no. -
Si elimina el archivo
X.class
completo, la claseZ
aún se ejecuta correctamente.Z
no tiene dependencia de tiempo de ejecución enX
Mientras que si elimina el archivoY.class
, la claseZ
no puede inicializarse con unaClassNotFoundException: 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 deYFOO
.
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 camposstatic
). 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 clavefinal
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 constantesstatic
.
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
.