Mientras buscaba en la Especificación del lenguaje Java para responder a esta pregunta , aprendí que
Antes de que se inicialice una clase, se debe inicializar su superclase directa, pero las interfaces implementadas por la clase no se inicializan. De manera similar, las superinterfaces de una interfaz no se inicializan antes de que se inicialice la interfaz.
Por mi propia curiosidad, lo probé y, como esperaba, la interfaz InterfaceType
no se inicializó.
public class Example {
public static void main(String[] args) throws Exception {
InterfaceType foo = new InterfaceTypeImpl();
foo.method();
}
}
class InterfaceTypeImpl implements InterfaceType {
@Override
public void method() {
System.out.println("implemented method");
}
}
class ClassInitializer {
static {
System.out.println("static initializer");
}
}
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public void method();
}
Este programa imprime
implemented method
Sin embargo, si la interfaz declara un default
método, se produce la inicialización. Considere la InterfaceType
interfaz dada como
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
entonces el mismo programa anterior imprimiría
static initializer
implemented method
En otras palabras, static
se inicializa el campo de la interfaz ( paso 9 en el Procedimiento de inicialización detallado ) y static
se ejecuta el inicializador del tipo que se está inicializando. Esto significa que se inicializó la interfaz.
No pude encontrar nada en el JLS que indique que esto debería suceder. No me malinterpretes, entiendo que esto debería suceder en caso de que la clase de implementación no proporcione una implementación para el método, pero ¿y si lo hace? ¿Falta esta condición en la Especificación del lenguaje Java, me perdí algo o lo estoy interpretando incorrectamente?
fuente
interface
en Java no se debería definir ningún método concreto. Así que me sorprende que elInterfaceType
código se haya compilado.default
métodos .Respuestas:
¡Este es un tema muy interesante!
Parece que la sección 12.4.1 de JLS debería cubrir esto definitivamente. Sin embargo, el comportamiento de Oracle JDK y OpenJDK (javac y HotSpot) difiere de lo que se especifica aquí. En particular, el ejemplo 12.4.1-3 de esta sección cubre la inicialización de la interfaz. El ejemplo de la siguiente manera:
Su salida esperada es:
y de hecho obtengo el resultado esperado. Sin embargo, si se agrega un método predeterminado a la interfaz
I
,la salida cambia a:
lo que indica claramente que la interfaz
I
se está inicializando donde no estaba antes. La mera presencia del método predeterminado es suficiente para activar la inicialización. El método predeterminado no tiene que ser llamado, anulado o incluso mencionado, ni la presencia de un método abstracto desencadena la inicialización.Mi especulación es que la implementación de HotSpot quería evitar agregar la verificación de inicialización de clase / interfaz en la ruta crítica de la
invokevirtual
llamada. Antes de Java 8 y los métodos predeterminados,invokevirtual
nunca podría terminar ejecutando código en una interfaz, por lo que esto no surgió. Uno podría pensar que esto es parte de la etapa de preparación de la clase / interfaz ( JLS 12.3.2 ) que inicializa cosas como tablas de métodos. Pero quizás esto fue demasiado lejos y accidentalmente hizo una inicialización completa en su lugar.He planteado esta pregunta en la lista de correo del desarrollador-compilador de OpenJDK. Ha habido una respuesta de Alex Buckley (editor de JLS) en la que plantea más preguntas dirigidas a los equipos de implementación de JVM y lambda. También señala que hay un error en la especificación aquí donde dice "T es una clase y se invoca un método estático declarado por T" también debería aplicarse si T es una interfaz. Por lo tanto, es posible que haya errores de especificación y HotSpot aquí.
Divulgación : trabajo para Oracle en OpenJDK. Si la gente piensa que esto me da una ventaja injusta para obtener la recompensa adjunta a esta pregunta, estoy dispuesto a ser flexible al respecto.
fuente
La interfaz no se inicializa porque el campo constante
InterfaceType.init
, que se inicializa mediante un valor no constante (llamada al método), no se utiliza en ningún lugar.Se sabe en el momento de la compilación que el campo constante de la interfaz no se usa en ningún lugar y que la interfaz no contiene ningún método predeterminado (en java-8), por lo que no es necesario inicializar o cargar la interfaz.
La interfaz se inicializará en los siguientes casos,
En caso de métodos predeterminados , está implementando
InterfaceType
. Entonces, siInterfaceType
contendrá cualquier método predeterminado, será HEREDADO (usado) en la implementación de la clase. Y la inicialización estará en la imagen.Pero, si está accediendo al campo constante de la interfaz (que se inicializa de manera normal), la inicialización de la interfaz no es necesaria.
Considere el siguiente código.
En el caso anterior, la interfaz se inicializará y cargará porque está utilizando el campo
InterfaceType.init
.No estoy dando el ejemplo del método predeterminado, ya que ya lo dio en su pregunta.
La especificación y el ejemplo del lenguaje Java se dan en JLS 12.4.1 (El ejemplo no contiene métodos predeterminados).
No puedo encontrar JLS para los métodos predeterminados, puede haber dos posibilidades
fuente
default
método y se inicializa una clase que implementa la interfaz.El archivo instanceKlass.cpp de OpenJDK contiene el método de inicialización
InstanceKlass::initialize_impl
que corresponde al procedimiento de inicialización detallado en JLS, que se encuentra de forma análoga en la sección Inicialización de la especificación de JVM.Contiene un nuevo paso que no se menciona en el JLS y no en el libro de JVM al que se hace referencia en el código:
Por tanto, esta inicialización se ha implementado explícitamente como un nuevo Paso 7.5 . Esto indica que esta implementación siguió alguna especificación, pero parece que la especificación escrita en el sitio web no se ha actualizado en consecuencia.
EDITAR: Como referencia, el compromiso (¡de octubre de 2012!) Donde se ha incluido el paso respectivo en la implementación: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
EDIT2: Casualmente, encontré este documento sobre métodos predeterminados en hotspot que contiene una nota al margen interesante al final:
fuente
Intentaré argumentar que la inicialización de una interfaz no debería causar ningún efecto secundario de canal lateral de los que dependan los subtipos, por lo tanto, ya sea que se trate de un error o no, o de cualquier forma en que Java lo solucione, no debería importar. la aplicación en la que se inicializan las interfaces.
En el caso de a
class
, está bien aceptado que puede causar efectos secundarios de los que dependen las subclases. Por ejemploCualquier subclase de
Foo
esperaría ver $ 1000 en el banco, en cualquier parte del código de la subclase. Por lo tanto, la superclase se inicializa antes que la subclase.¿No deberíamos hacer lo mismo con las superintefaces también? Desafortunadamente, el orden de las superinterfaces no se supone que sea significativo, por lo tanto, no existe un orden bien definido para inicializarlas.
Así que es mejor que no establezcamos este tipo de efectos secundarios en las inicializaciones de la interfaz. Después de todo,
interface
no está diseñado para estas características (campos / métodos estáticos) que acumulamos por conveniencia.Por lo tanto, si seguimos ese principio, no nos preocupará en qué orden se inicializan las interfaces.
fuente