92 votes

Quelle est une interface avec une méthode par défaut initialisée ?

Lors de la recherche par le biais de la Java Langage de Spécification pour répondre à cette question, j'ai appris que

Devant une classe est initialisée, sa super-classe directe doit être initialisé, mais les interfaces implémentées par la classe ne sont pas initialisé. De même, la superinterfaces d'une interface sont pas initialisé avant que l'interface est initialisé.

Pour ma propre curiosité, j'ai essayé et, vraiment, l'interface InterfaceType n'a pas été initialisé

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();
}

Ce programme imprime

implemented method

Toutefois, si l'interface déclare default méthode, puis l'initialisation se produit. Envisager l' InterfaceType interface étant donné que

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

puis le même programme ci-dessus serait d'imprimer

static initializer
implemented method

En d'autres termes, l' static domaine de l'interface est initialisé (étape 9 dans la Procédure d'Initialisation) et l' static de l'initialiseur de type en cours d'initialisation est exécutée. Cela signifie que l'interface a été initialisé.

Je ne pouvais pas trouver quoi que ce soit dans le JLS pour indiquer que ce qui doit arriver. Ne vous méprenez pas, je comprends que cela doit se faire dans le cas où la mise en œuvre de la classe n'est pas de fournir une implémentation de la méthode, mais si ce n'? Est-ce la condition manquante à partir de la Java Language Specification, j'ai oublié quelque chose, ou suis-je en l'interprétant à tort?

81voto

Stuart Marks Points 8927

C'est une très bonne question!

Il semble que JLS section 12.4.1 devrait couvrir ce définitivement. Toutefois, le comportement d'Oracle JDK et OpenJDK (javac et HotSpot) diffère de ce qui est spécifié ici. En particulier, l'Exemple 12.4.1-3 de la présente section couvre l'initialisation de l'interface. L'exemple comme suit:

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;
    }
}

Sa sortie attendue est:

1
j=3
jj=4
3

et en effet je obtenir le résultat attendu. Toutefois, si une méthode par défaut est ajouté à l'interface I,

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

les changements de sortie:

1
ii=2
j=3
jj=4
3

ce qui indique clairement une interface I est en cours d'initialisation où il n'était pas avant! La simple présence de la méthode par défaut est suffisant pour déclencher l'initialisation. La méthode par défaut ne doit pas être appelé ou ignorés, ou même pas mentionné, ni la présence d'un résumé de la méthode de déclenchement de l'initialisation.

À mon avis, est que le HotSpot de la mise en œuvre voulais éviter l'ajout de la classe/initialisation de l'interface de vérification dans le chemin critique de l' invokevirtual appel. Avant Java 8 et méthodes par défaut, invokevirtual pourrait ne jamais l'exécution de code dans une interface, de sorte que cela ne se pose pas. On pourrait penser que c'est une partie de la classe/de l'interface de la phase de préparation (JLS 12.3.2) qui initialise les choses comme les tables de méthodes. Mais peut-être que c'est allé trop loin et a accidentellement fait complète de l'initialisation de la place.

J'ai soulevé cette question sur l'OpenJDK compilateur dev mailing list. Il y a eu une réponse de Alex Buckley (rédacteur en chef de la JLS), dans laquelle il soulève plus de questions adressées à la JVM et lambda équipes de mise en œuvre. Il note également qu'il y a un bug dans la spec ici, où il est dit "T est une classe et une méthode statique déclaré par T est appelé" devrait également s'appliquer si T est une interface. Il se peut donc qu'il y a à la fois la spécification des points d'accès et les bugs ici.

Divulgation: je travaille pour Oracle sur OpenJDK. Si les gens pensent que cela me donne un avantage injuste à l'obtention de la prime attachée à cette question, je suis prêt à être flexible sur les.

13voto

L'interface n'est pas initialisé car le champ constant InterfaceType.init , ce qui est en cours d'initialisation en non valeur constante (appel de méthode), n'est pas utilisé n'importe où.

Il est connu au moment de la compilation que champ constant de l'interface n'est pas utilisé n'importe où, et l'interface n'est pas contenant une méthode par défaut (En java-8) donc il n'est pas nécessaire d'initialiser ou de charger l'interface.

L'Interface sera initialisé dans les cas suivants,

  • à champ constant est utilisé dans votre code.
  • L'Interface contient une méthode par défaut (Java 8)

En cas de Défaut Méthodes, Vous mettez en œuvre InterfaceType. Donc, Si InterfaceType contient toutes les méthodes par défaut, Il sera HÉRITÉE (utilisé) dans la mise en œuvre de la classe. Et l'Initialisation est dans l'image.

Mais, Si vous accédez à champ constant de l'interface (qui est initialisé en mode normal), L'initialisation de l'interface n'est pas nécessaire.

Envisager de suivre le code.

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();
}

Dans le cas ci-dessus, l'Interface sera initialisé et chargé parce que vous êtes à l'aide du champ InterfaceType.init.

Je ne donne pas la méthode par défaut exemple comme vous l'avez déjà vu que, dans votre question.

Java langage de spécification et l'exemple est donné dans JLS 12.4.1 (Exemple ne contient pas de méthodes par défaut.)


Je ne peux pas trouver JLS des méthodes par Défaut, il peut y avoir deux possibilités

  • Java personnes oublié de prendre en considération le cas de la méthode par défaut. (Spécification Doc bug.)
  • Ils ont juste renvoyer la valeur par défaut des méthodes non-constante de membres de de l'interface. (Mais ne mentionne aucun où, de nouveau, Spécifications Doc bug.)

10voto

Marco13 Points 14743

L' instanceKlass.cpp fichier à partir de l'OpenJDK contient la méthode d'initialisation InstanceKlass::initialize_impl qui correspond à l' Détaillée de la Procédure d'Initialisation dans le JLS, qui est, d'une façon analogue trouvé dans l' Initialisation de la section dans la JVM Spec.

Il contient une nouvelle étape qui n'est pas mentionné dans le JLS et pas dans la JVM livre qui est mentionnée dans le code:

// 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);
    ....
    }
  }
}

Donc, cette initialisation a été mis en œuvre explicitement comme une nouvelle Étape 7.5. Cela indique que cette mise en œuvre, suivi des spécifications, mais il semble que la spécification écrite sur le site web n'a pas été mis à jour en conséquence.

EDIT: Comme une référence, la validation (à partir de octobre 2012!) où l'étape concernée a été inclus dans la mise en œuvre: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: comme par hasard, j'ai trouvé ce Document sur les méthodes par défaut dans hotspot qui contient une note intéressante à la fin:

3.7 Divers

Parce que les interfaces ont maintenant bytecode en eux, nous devons initialiser à la temps de mise en œuvre de la classe est initialisée.

1voto

bayou.io Points 3680

Je vais essayer de faire une initialisation de l'interface ne devrait pas poser de côté-canal d'effets secondaires que les sous-types dépendent, par conséquent, de savoir si c'est un bug ou non, ou selon la manière dont la Java le corrige, il ne devrait pas question pour l'application de l'ordre dans lequel les interfaces sont initialisés.

Dans le cas d'un class, il est bien admis qu'il peut provoquer des effets secondaires que les sous-classes dépendent. Par exemple

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

Toute sous-classe d' Foo attendons à ce qu'ils vont voir de 1000 $à la banque, n'importe où dans la sous-classe code. Par conséquent, la super-classe est initialisée avant la sous-classe.

Ne devrions-nous pas faire la même chose pour superintefaces ainsi? Malheureusement, l'ordre de superinterfaces ne sont pas censés être importants, donc il n'y a pas d'ordre bien défini dans lequel les initialiser.

Nous avons donc préférable de ne pas établir ce genre d'effets secondaires dans l'interface d'initialisation. Après tout, interface n'est pas fait pour ces fonctionnalités (champs statiques/méthodes), nous avons velours pour plus de commodité.

Donc si on suit ce principe, ça va être aucun intérêt pour nous de l'ordre dans lequel les interfaces sont initialisés.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X