¿Cuándo se inicializa una interfaz con un método predeterminado?


Mientras buscaba a través de la Especificación del Lenguaje Java para responder esta pregunta , aprendí que

Antes de inicializar una clase, su superclase directa debe ser inicializadas, pero las interfaces implementadas por la clase no son inicialización. Del mismo modo, las superinterfaces de una interfaz no son inicializado antes de inicializar la interfaz.

Por mi propia curiosidad, lo probé y, como era de esperar, la interfaz InterfaceType no fue inicializado.

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 método default, entonces se produce la inicialización. Considere la interfaz InterfaceType 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, se inicializa el campo static de la interfaz ( paso 9 en el Procedimiento de Inicialización Detallado) y se ejecuta el inicializador static del tipo que se inicializa. Esto significa que el la interfaz fue inicializada.

No pude encontrar nada en la JLS que indicara que esto debería suceder. No me malinterpretes, entiendo que esto debería suceder en caso de que la clase implementadora 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 he perdido algo o la estoy interpretando mal?

Author: Sotirios Delimanolis, 2014-04-16

4 answers

Este es un tema muy interesante!

Parece que La sección 12.4.1 de la JLS debería cubrir esto definitivamente. Sin embargo, el comportamiento de Oracle JDK y OpenJDK (javac y HotSpot) difiere de lo especificado aquí. En particular, el Ejemplo 12.4.1 - 3 de esta sección cubre la inicialización de la interfaz. El ejemplo es el siguiente:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Su salida esperada es:

1
j=3
jj=4
3

Y de hecho obtengo la salida esperada. Sin embargo, si se agrega un método predeterminado a la interfaz I,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

La salida cambia a:

1
ii=2
j=3
jj=4
3

Lo que indica claramente que la interfaz I se está inicializando donde no lo 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 o sobrescrito 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 inicialización de clase / interfaz comprobación de la ruta crítica de la llamada invokevirtual. Antes de Java 8 y los métodos predeterminados, invokevirtual nunca podí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 tal vez esto fue demasiado lejos y accidentalmente hizo la inicialización completa en su lugar.

He planteado esta pregunta en la lista de correo OpenJDK compiler-dev. Ha habido un 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, puede ser 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 injusta ventaja en conseguir la recompensa adjunta a esta pregunta, estoy dispuesto a ser flexible al respecto.

 80
Author: Stuart Marks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-23 14:21:41

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 ninguna parte.

Se sabe en tiempo de compilación que el campo constante de la interfaz no se usa en ninguna parte, y la interfaz no contiene ningún método predeterminado (en java-8), por lo que no hay necesidad de inicializar o cargar la interfaz.

La interfaz se inicializará en los siguientes casos,

  • el campo constante se usa en su código.
  • La interfaz contiene un método predeterminado (Java 8)

En el caso de Métodos predeterminados, Está implementando InterfaceType. Por lo tanto, si InterfaceType contendrá cualquier método predeterminado, será HEREDADO (utilizado) en la clase de implementación. 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 seguir codificar.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        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();
}

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 como ya lo dio en su pregunta.

La especificación del lenguaje Java y el ejemplo 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

  • La gente de Java olvidó considerar el caso del método por defecto. (Especificación Doc bug.)
  • Simplemente se refieren a los métodos predeterminados como miembros no constantes de interfaz. (Pero no mencionó donde, de nuevo Especificación Doc bug.)
 13
Author: Not a bug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-23 07:04:20

El instanceKlass.el archivo cpp del OpenJDK contiene el método de inicialización InstanceKlass::initialize_implque corresponde al Procedimiento de Inicialización Detallado en el JLS, que se encuentra análogamente en la sección Inicialización en la especificación JVM.

Contiene un nuevo paso que no se menciona en el JLS y no en el libro JVM al que se hace referencia en el código:

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

Así que 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 commit (de octubre de 2012!) cuando la etapa respectiva se haya incluido en la aplicación: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: Casualmente, encontré este Documento sobre métodos predeterminados en hotspot que contiene un interesante nota al final:

3.7 Varios

Debido a que las interfaces ahora tienen bytecode en ellas, debemos inicializarlas en el tiempo que se inicializa una clase de implementación.

 10
Author: Marco13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-04 11:28:22

Voy a tratar de hacer un caso de que una inicialización de interfaz no debe causar ningún efecto secundario de canal lateral que los subtipos dependen de, por lo tanto, si esto es un error o no, o de cualquier manera que Java lo arregle, no debe importar a la aplicación en qué orden se inicializan las interfaces.

En el caso de un class, es bien aceptado que puede causar efectos secundarios de los que dependen las subclases. Por ejemplo

class Foo{
    static{
        Bank.deposit($1000);
...

Cualquier subclase de Foo esperaría que vieran 1 1000 en el banco, en cualquier parte del código de la subclase. Por lo tanto, la superclase se inicializa antes de la subclase.

¿No deberíamos hacer lo mismo con superintefaces también? Desafortunadamente, el orden de las superinterfaces no se supone que sea significativo, por lo tanto no hay un orden bien definido en el que inicializarlas.

Así que es mejor no establecer este tipo de efectos secundarios en inicializaciones de interfaz. Después de todo, interface no está destinado a estas características (estática campos / métodos) nos apilamos para mayor comodidad.

Por lo tanto, si seguimos ese principio, no nos preocupará en qué orden se inicializan las interfaces.

 1
Author: ZhongYu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-22 22:38:06