sirve - superclase java ejemplo
¿Por qué no se invoca el inicializador estático de una subclase cuando se invoca un método estático declarado en su superclase en la subclase? (7)
Dadas las siguientes clases:
public abstract class Super {
protected static Object staticVar;
protected static void staticMethod() {
System.out.println( staticVar );
}
}
public class Sub extends Super {
static {
staticVar = new Object();
}
// Declaring a method with the same signature here,
// thus hiding Super.staticMethod(), avoids staticVar being null
/*
public static void staticMethod() {
Super.staticMethod();
}
*/
}
public class UserClass {
public static void main( String[] args ) {
new UserClass().method();
}
void method() {
Sub.staticMethod(); // prints "null"
}
}
No estoy apuntando a respuestas como "Porque está especificado de esta manera en el JLS". Sé que es así, ya que JLS, 12.4.1 Cuando se produce la inicialización, se lee simplemente:
Una clase o tipo de interfaz T se inicializará inmediatamente antes de la primera aparición de cualquiera de los siguientes:
...
T es una clase y se invoca un método estático declarado por T
...
Me interesa si hay una buena razón por la que no hay una oración como:
- T es una subclase de S y un método estático declarado por S se invoca en T.
Creo que tiene que ver con esta parte de la especificación de jvm:
Cada cuadro (§2.6) contiene una referencia al grupo de constantes de tiempo de ejecución (§2.5.5) para el tipo del método actual para admitir el enlace dinámico del código del método. El código de archivo de clase para un método se refiere a los métodos que se invocan y las variables a las que se puede acceder mediante referencias simbólicas. La vinculación dinámica convierte estas referencias de métodos simbólicos en referencias de métodos concretos, cargando las clases según sea necesario para resolver los símbolos aún no definidos, y traduce los accesos de las variables a las compensaciones apropiadas en las estructuras de almacenamiento asociadas con la ubicación en tiempo de ejecución de estas variables.
Esta vinculación tardía de los métodos y las variables realiza cambios en otras clases que un método utiliza con menos probabilidad de romper este código.
En el capítulo 5 de la especificación de jvm también mencionan: Una clase o interfaz C puede inicializarse, entre otras cosas, como resultado de:
La ejecución de cualquiera de las instrucciones de Java Virtual Machine nuevas, getstatic, putstatic o invokestatic que hacen referencia a C (§new, §getstatic, §putstatic, §invokestatic). Estas instrucciones hacen referencia a una clase o interfaz directa o indirectamente a través de una referencia de campo o una referencia de método.
...
Al ejecutarse una instrucción getstatic, putstatic o invokestatic, la clase o interfaz que declaró el campo o método resuelto se inicializa si aún no se ha inicializado.
Me parece que la primera parte de la documentación indica que cualquier referencia simbólica se resuelve y se invoca simplemente sin tener en cuenta de dónde proviene. Esta documentación sobre la resolución de métodos tiene lo siguiente que decir al respecto:
[M] ethod resolution intenta localizar el método al que se hace referencia en C y sus superclases:
Si C declara exactamente un método con el nombre especificado por la referencia del método, y la declaración es un método polimórfico de firma (§2.9), entonces la búsqueda del método es exitosa. Se resuelven todos los nombres de clase mencionados en el descriptor (§5.4.3.1).
El método resuelto es la declaración del método polimórfico de la firma. No es necesario que C declare un método con el descriptor especificado por la referencia del método.
De lo contrario, si C declara un método con el nombre y el descriptor especificados por la referencia del método, la búsqueda del método tiene éxito.
De lo contrario, si C tiene una superclase, el paso 2 de la resolución del método se invoca recursivamente en la superclase directa de C.
Entonces, el hecho de que se llame desde una subclase parece simplemente ignorarse. ¿Por qué hacerlo de esta manera? En la documentación que usted proporcionó dicen:
La intención es que una clase o tipo de interfaz tenga un conjunto de inicializadores que lo pongan en un estado coherente, y que este estado sea el primer estado observado por otras clases.
En tu ejemplo, alteras el estado de Super cuando Sub se inicializa estáticamente. Si la inicialización ocurriera cuando llamara a Sub.staticMethod, obtendría un comportamiento diferente por lo que el jvm considera el mismo método. Esto podría ser la incoherencia que estaban hablando acerca de evitar.
Además, aquí hay algunos de los códigos de archivo de clase descompilados que ejecutan staticMethod, mostrando el uso de invokestatic:
Constant pool:
...
#2 = Methodref #18.#19 // Sub.staticMethod:()V
...
Code:
stack=0, locals=1, args_size=1
0: invokestatic #2 // Method Sub.staticMethod:()V
3: return
Cuando se ejecuta el bloque estático Inicializadores estáticos
Un inicializador estático declarado en una clase se ejecuta cuando la clase se inicializa
cuando llamas a Sub.staticMethod();
eso significa que la clase no está inicializada. Solo son referencias
Cuando se inicializa una clase
Cuando una clase se inicializa en Java Después de la carga de la clase, se lleva a cabo la inicialización de la clase, lo que significa la inicialización de todos los miembros estáticos de la clase. Una clase se inicializa en Java cuando:
1) se crea una Instancia de clase usando la palabra clave new () o usando la reflexión usando class.forName (), que puede lanzar la excepción ClassNotFoundException en Java.
2) Se invoca un método estático de Clase.
3) Se asigna un campo estático de Clase.
4) se utiliza un campo estático de clase que no es una variable constante.
5) si la Clase es una clase de nivel superior y se ejecuta una declaración de afirmación lexicamente anidada dentro de la clase.
Cuando una clase se carga e inicializa en JVM - Java
es por eso que obtiene nulo (valor predeterminado de la variable de instancia).
public class Sub extends Super {
static {
staticVar = new Object();
}
public static void staticMethod() {
Super.staticMethod();
}
}
en este caso, la clase se inicializa y se obtiene el código hash del new object()
. Si no anula staticMethod()
significa que su método de súper clase referente y la staticMethod()
no están inicializados.
De acuerdo con este article , cuando se llama método estático o se usa el archivo estático de una clase, solo se inicializará esa clase.
El JLS permite específicamente que la JVM evite cargar la subclase, está en la sección citada en la pregunta:
Una referencia a un campo estático (§8.3.1.1) provoca la inicialización de solo la clase o interfaz que realmente lo declara, incluso aunque se pueda hacer referencia a él a través del nombre de una subclase, una subinterfaz o una clase que implementa una interfaz.
La razón es evitar tener las clases de carga JVM innecesariamente. Inicializar variables estáticas no es un problema porque no se hace referencia de ninguna manera.
La razón es bastante simple: para que JVM no haga un trabajo extra prematuramente (Java es perezoso en su naturaleza).
Ya sea que escriba Super.staticMethod()
o Sub.staticMethod()
, se llama a la misma implementación. Y la implementación de este padre normalmente no depende de las subclases. Los métodos estáticos de Super
no se supone que accedan a los miembros de Sub
, así que, ¿cuál es el punto de inicializar a Sub
?
Tu ejemplo parece ser artificial y no está bien diseñado.
Hacer que la subclase vuelva a escribir los campos estáticos de la superclase no suena como una buena idea. En este caso, un resultado de los métodos de Super dependerá de qué clase se toque primero. Esto también dificulta tener varios hijos de Super con su propio comportamiento. Para abreviar, los miembros estáticos no son para el polimorfismo, eso es lo que dicen los principios OOP.
Tenga cuidado en su título, los campos estáticos y los métodos NO se heredan. Esto significa que cuando comenta staticMethod()
en Sub
, Sub.staticMethod()
realidad llama a Super.staticMethod()
entonces el inicializador Sub
static no se ejecuta.
Sin embargo, la pregunta es más interesante de lo que pensé a primera vista: desde mi punto de vista, esto no debería compilarse sin una advertencia, como cuando se llama a un método estático en una instancia de la clase.
EDITAR: Como @GeroldBroser lo señaló, la primera declaración de esta respuesta es incorrecta. Los métodos estáticos también se heredan, pero nunca se anulan, simplemente se ocultan. Dejo la respuesta como está para la historia.
por alguna razón, jvm piensa que el bloqueo estático no es bueno y no se ejecuta
Creo que es porque no está utilizando ningún método para la subclase, por lo que jvm no ve ninguna razón para "iniciar" la clase en sí misma, la llamada al método está enlazada estáticamente al padre en el momento de la compilación: hay un enlace tardío para los métodos estáticos
static {
System.out.println("init");
staticVar = new Object();
}
Agrega algún otro método y llámalo antes del sub
Sub.someOtherMethod();
new UsersClass().method();
o hacer Class.forName("Sub");
explícito Class.forName("Sub");
Class.forName("Sub");
new UsersClass().method();