Extraño comportamiento usando llaves en Java
braces (9)
Cuando ejecuto el siguiente código:
public class Test {
Test(){
System.out.println("1");
}
{
System.out.println("2");
}
static {
System.out.println("3");
}
public static void main(String args[]) {
new Test();
}
}
Espero obtener la salida en este orden:
1
2
3
pero lo que obtuve está en orden inverso:
3
2
1
¿Alguien puede explicar por qué sale en orden inverso?
================
Además, cuando creo más de una instancia de Test
:
new Test();
new Test();
new Test();
new Test();
el bloque estático se ejecuta solo en el primer momento.
Explicación completa
El orden de ejecución es como,
- bloque estático
- bloque de instancia
- constructor
Explicación
El bloqueo estático siempre se invocará una sola vez desde el principio siempre que se acceda a la clase por cualquier medio, en su caso, que es cuando ejecuta el programa. (Para eso está diseñado el bloque estático). No depende de las instancias, por lo tanto, no se vuelve a llamar cuando se crean nuevas instancias.
Luego se llamará al bloque de inicialización de Instancia para cada instancia creada y luego se creará el constructor para cada instancia. Porque ambos se pueden usar para crear instancias de la instancia.
¿El bloque de inicialización de instancias se llama realmente antes del constructor?
Después de la compilación, el código se convertirá en
public class Test {
Test(){
super();
System.out.println("2");
System.out.println("1");
}
static {
System.out.println("3");
}
public static void main(String args[]) {
new Test();
}
}
Como puede ver, la instrucción escrita en el bloque de instancias se convierte en parte del constructor. Por lo tanto, se ejecuta antes de las declaraciones ya escritas en el constructor.
El compilador de Java copia bloques de inicializador en cada constructor. Por lo tanto, este enfoque se puede usar para compartir un bloque de código entre múltiples constructores.
3 - es un inicializador estático, se ejecuta una vez cuando se carga la clase, lo que sucede primero.
2 - es un bloque de inicializador, el compilador de java realmente copiará esto en cada constructor, por lo que puede compartir alguna inicialización entre contructors si lo desea. Raramente usado.
1 - se ejecutará cuando construyas el objeto, después de (3) y (2) ..
Debido a que el código static{}
se ejecuta cuando la clase se inicializa por primera vez dentro de la JVM (es decir, incluso antes de llamar a main()
), se invoca a la instancia {}
cuando se inicializa una instancia, antes de que se construya, y luego el constructor llamado después de todo lo que está hecho.
He obtenido el código tipo bytecode aquí por ASM.
Creo que esto puede responder a su pregunta, explicando qué sucedió cuando se creó un objeto en esta ocasión.
public class Test {
static <clinit>() : void
GETSTATIC System.out : PrintStream
LDC "3"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN
<init>() : void
ALOAD 0: this
INVOKESPECIAL Object.<init>() : void
GETSTATIC System.out : PrintStream
LDC "2"
INVOKEVIRTUAL PrintStream.println(String) : void
GETSTATIC System.out : PrintStream
LDC "1"
INVOKEVIRTUAL PrintStream.println(String) : void
RETURN
public static main(String[]) : void
NEW Test
INVOKESPECIAL Test.<init>() : void
RETURN
}
podemos ver que LDC "3"
está en el "clinit", este es un inicializador de clase.
La vida útil de un objeto generalmente es: carga de clase -> clase de vinculación -> inicialización de clase -> instanciación de objetos -> uso -> GC. Es por eso que 3 aparece primero. Y como esto está en el nivel de clase, no en el nivel de objeto, aparecerá una vez ya que el tipo de clase se cargará una vez. Para obtener más información, haga referencia a dentro de la máquina virtual Java2: tiempo de vida de un tipo
LDC "2"
y `LDC "1"
está en" init ", el constructor.
La razón por la cual está en este orden es: Constructor primero ejecutará algunas instrucciones implícitas como súper constructor y código en el {} de una clase, luego ejecutará el código que está en su construtor explícito.
Eso es lo que un compilador hará con el archivo java.
Los bloques estáticos se ejecutan primero.
Y luego, los bloques de inicialización de la instancia de instancia
Por favor, consulte JLS, por ejemplo, intializadores
{
// instrucción sop
}
no puede tener una declaración de devolución dentro del bloque de inicialización de instancia, al igual que los constructores.
No parece que nadie haya dicho por qué el 3 solo se imprime una vez explícitamente. Entonces, agregaría que esto está relacionado con por qué se imprime primero.
El código estáticamente definido se marca como separado de cualquier instancia particular de la clase. En general, se puede considerar que el código estáticamente definido no es de ninguna clase (por supuesto, hay cierta invalidez en esa declaración cuando se considera el alcance). Por lo tanto, ese código se ejecuta una vez que se carga la clase, como se indicó anteriormente, como en, no se llama cuando se construye una instancia de Test()
, por lo que llamar al constructor varias veces no dará como resultado el código estático que se está ejecutando.
El código entre corchetes que contiene el 2 se antepone al constructo, como se inició anteriormente, porque es una especie de precondición para todos los constructores de la clase. No sabes qué sucederá en los constructores para Test, pero tienes la garantía de que todos comienzan imprimiendo 2 . Por lo tanto, esto sucede antes que cualquier cosa en cualquier constructor específico, y se llama cada vez que se llama a un (ny) constructor.
Primero, la clase se carga en la JVM y ocurre la inicialización de la clase. Durante este paso, se ejecutan bloques estáticos. "{...}" es solo un equivalente sintáctico de "static {...}". Como ya hay un bloque "static {...}" en el código, se le agregará "{...}". Es por eso que tienes 3 impresos antes de 2.
Luego, una vez que se carga la clase, java.exe (que supuse que habías ejecutado desde la línea de comandos) buscará y ejecutará el método principal. El método estático principal inicializa la instancia cuyo constructor se invoca, por lo que se imprime "1" al final.
Todo depende del orden de ejecución de las declaraciones de inicialización. Su prueba demuestra que este orden es:
- Bloques de inicialización estática
- Bloques de inicialización de instancias
- Constructores
Editar
Gracias por los comentarios, ahora puedo citar la parte apropiada en la especificación de JVM. Here está, el procedimiento de inicialización detallado.
Test(){System.out.println("1");}
{System.out.println("2");}
static{System.out.println("3");}
las cosas estáticas se ejecutan primero, {System.out.println("2");}
no es parte de una función, debido a su alcance se llama primero, y Test(){System.out.println("1");}
se llama último porque los otros dos se llaman primero