java - setborder - borderfactory
Comportamiento extraño de Java con calificadores estáticos y finales (4)
Estos son los pasos tomados cuando ejecuta su programa:
-
Antes de poder ejecutar
main
, la claseTest
debe inicializarse ejecutando inicializadores estáticos en orden de aparición. -
Para inicializar el campo
me
, comience a ejecutar lanew Test()
. -
Imprime el valor de
I
Como el tipo de campo esInteger
, lo que parece una constante de tiempo de compilación4
convierte en un valor calculado (Integer.valueOf(4)
). El inicializador de este campo aún no se ha ejecutado, imprimiendo el valor inicialnull
. -
Imprime el valor de
S
Dado que se inicializa con una constante de tiempo de compilación, este valor se incorpora al sitio de referencia, imprimiendoabc
. -
new Test()
, ahora se ejecuta el inicializador paraI
Lección: si confía en singletons estáticos inicializados con entusiasmo, coloque la declaración de singleton como la última declaración de campo estático, o recurra a un bloque de inicializador estático que se produce después de todas las demás declaraciones estáticas. Eso hará que la clase parezca totalmente inicializada en el código de construcción del singleton.
Esta pregunta ya tiene una respuesta aquí:
- Campos finales inicialización orden 2 respuestas
En nuestro equipo encontramos un comportamiento extraño en el que utilizamos calificadores
static
y
final
.
Esta es nuestra clase de prueba:
public class Test {
public static final Test me = new Test();
public static final Integer I = 4;
public static final String S = "abc";
public Test() {
System.out.println(I);
System.out.println(S);
}
public static Test getInstance() { return me; }
public static void main(String[] args) {
Test.getInstance();
}
}
Cuando ejecutamos el método
main
, obtenemos un resultado de:
null
abc
Lo entendería si escribiera valores
null
ambas ocasiones, ya que el código de los miembros de la clase estática se ejecuta de arriba a abajo.
¿Alguien puede explicar por qué está ocurriendo este comportamiento?
Su
Test
compila en:
public class Test {
public static final Test me;
public static final Integer I;
public static final String S = "abc";
static {
me = new Test();
I = Integer.valueOf(4);
}
public Test() {
System.out.println(I);
System.out.println("abc");
}
public static Test getInstance() { return me; }
public static void main(String[] args) {
Test.getInstance();
}
}
Como puede ver, se llama al constructor de
Test
antes de
I
se inicialice.
Es por eso que imprime
"null"
para
I
Si tuviera que cambiar el orden de declaración para
me
y para
me
, obtendría el resultado esperado porque se inicializaría antes de que se invoque el constructor.
También puede cambiar el tipo de
I
de
Integer
a
int
.
Debido a que
4
necesita autoboxing (es decir, envuelto en un objeto
Integer
), no es una constante de tiempo de compilación y es parte del bloque de inicializador estático.
Sin embargo, si el tipo fuera
int
, el número
4
sería una constante de tiempo de compilación, por lo que no necesitaría ser inicializado explícitamente.
Como
"abc"
es una constante de tiempo de compilación, el valor de
S
se imprime como se esperaba.
Si reemplazaras,
public static final String S = "abc";
con,
public static final String S = new String("abc");
Entonces notarías que la salida de
S
es
"null"
.
¿Por qué pasa eso?
Por la misma razón por la
I
también genera
"null"
.
Los campos como estos que tienen valores literales constantes (que
no
necesitan autoboxing, como
String
) se atribuyen con el atributo
"ConstantValue"
cuando se compilan, lo que significa que su valor puede resolverse simplemente mirando el grupo constante de la clase, sin necesidad para ejecutar cualquier código.
Tiene un comportamiento extraño debido al tipo de datos
Integer
.
Con respecto a
JLS 12.4.2,
los campos estáticos se inicializan en el orden en que lo escribe, PERO las constantes de tiempo de compilación se inicializan primero.
Si no usa el tipo de contenedor
Integer
sino el tipo
int
, obtendrá el comportamiento que desea.
S
es una constante en tiempo de compilación, siguiendo las reglas de
JLS 15.28
.
Por lo tanto, cualquier aparición de
S
en el código se reemplaza con el valor que se conoce en tiempo de compilación.
Si cambia el tipo de
I
a
int
, también verá lo mismo para eso.