En cherchant dans la spécification du langage Java pour répondre à cette question , j'ai appris que
Avant qu'une classe ne soit initialisée, sa superclasse directe doit être initialisée, mais les interfaces implémentées par la classe ne sont pas initialisées. De même, les superinterfaces d'une interface ne sont pas initialisées avant que l'interface ne soit initialisée.
Pour ma propre curiosité, je l'ai essayé et, comme prévu, l'interface InterfaceType
n'a pas été initialisée.
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
Cependant, si l'interface déclare une default
méthode, l'initialisation se produit. Considérez l' InterfaceType
interface donnée comme
interface InterfaceType {
public static final ClassInitializer init = new ClassInitializer();
public default void method() {
System.out.println("default method");
}
}
alors le même programme ci-dessus imprimerait
static initializer
implemented method
En d'autres termes, le static
champ de l'interface est initialisé ( étape 9 de la procédure d'initialisation détaillée ) et l' static
initialiseur du type en cours d'initialisation est exécuté. Cela signifie que l'interface a été initialisée.
Je n'ai rien trouvé dans le JLS pour indiquer que cela devrait arriver. Ne vous méprenez pas, je comprends que cela devrait se produire au cas où la classe d'implémentation ne fournit pas d'implémentation pour la méthode, mais que se passe-t-il si c'est le cas? Cette condition est-elle absente de la spécification du langage Java, ai-je manqué quelque chose ou est-ce que je l'interprète mal?
la source
interface
Java ne devrait définir aucune méthode concrète. Je suis donc surpris que leInterfaceType
code ait été compilé.default
méthodes .Réponses:
C'est une question très intéressante!
Il semble que la section 12.4.1 de JLS devrait couvrir cela définitivement. Cependant, 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 cette section couvre l'initialisation de l'interface. L'exemple comme suit:
Son résultat attendu est:
et en effet j'obtiens la sortie attendue. Cependant, si une méthode par défaut est ajoutée à l'interface
I
,la sortie change en:
ce qui indique clairement que l'interface
I
est en cours d'initialisation là où elle ne l'était pas auparavant! La simple présence de la méthode par défaut suffit à déclencher l'initialisation. La méthode par défaut n'a pas besoin d'être appelée, remplacée ou même mentionnée, et la présence d'une méthode abstraite ne déclenche pas l'initialisation.Ma spéculation est que l'implémentation HotSpot voulait éviter d'ajouter une vérification d'initialisation de classe / interface dans le chemin critique de l'
invokevirtual
appel. Avant Java 8 et les méthodes par défaut,invokevirtual
on ne pouvait jamais finir par exécuter du code dans une interface, donc cela ne se produisait pas. On pourrait penser que cela fait partie de l'étape de préparation de classe / interface ( JLS 12.3.2 ) qui initialise des choses comme les tables de méthodes. Mais peut-être que cela est allé trop loin et a accidentellement effectué une initialisation complète à la place.J'ai soulevé cette question sur la liste de diffusion OpenJDK compiler-dev. Il y a eu une réponse d'Alex Buckley (rédacteur en chef du JLS) dans laquelle il soulève d'autres questions adressées aux équipes d'implémentation JVM et lambda. Il note également qu'il y a un bogue dans la spécification ici où il est dit "T est une classe et une méthode statique déclarée par T est invoquée" devrait également s'appliquer si T est une interface. Donc, il se peut qu'il y ait à la fois des bogues de spécification et HotSpot ici.
Divulgation : Je travaille pour Oracle sur OpenJDK. Si les gens pensent que cela me donne un avantage injuste pour obtenir la prime attachée à cette question, je suis prêt à être flexible à ce sujet.
la source
L'interface n'est pas initialisée car le champ constant
InterfaceType.init
, qui est initialisé par une valeur non constante (appel de méthode), n'est utilisé nulle part.Il est connu au moment de la compilation que le champ constant de l'interface n'est utilisé nulle part et que l'interface ne contient aucune méthode par défaut (en java-8), il n'est donc pas nécessaire d'initialiser ou de charger l'interface.
L'interface sera initialisée dans les cas suivants,
En cas de méthodes par défaut , vous implémentez
InterfaceType
. Donc, IfInterfaceType
contiendra des méthodes par défaut, Il sera HÉRITÉ (utilisé) dans la classe d'implémentation. Et l'initialisation sera dans l'image.Mais, si vous accédez au champ constant de l'interface (qui est initialisé de manière normale), l'initialisation de l'interface n'est pas requise.
Considérez le code suivant.
Dans le cas ci-dessus, l'interface sera initialisée et chargée car vous utilisez le champ
InterfaceType.init
.Je ne donne pas l'exemple de la méthode par défaut comme vous l'avez déjà indiqué dans votre question.
La spécification et l'exemple du langage Java sont donnés dans JLS 12.4.1 (l'exemple ne contient pas de méthodes par défaut.)
Je ne trouve pas JLS pour les méthodes par défaut, il peut y avoir deux possibilités
la source
default
méthode et une classe qui implémente l'interface est initialisée.Le fichier instanceKlass.cpp d'OpenJDK contient la méthode d'initialisation
InstanceKlass::initialize_impl
qui correspond à la procédure d'initialisation détaillée dans le JLS, qui se trouve de manière analogue dans la section Initialisation de la spécification JVM.Il contient une nouvelle étape qui n'est pas mentionnée dans le JLS et non dans le livre JVM auquel le code fait référence:
Cette initialisation a donc été implémentée explicitement en tant que nouvelle étape 7.5 . Cela indique que cette implémentation a suivi certaines spécifications, mais il semble que la spécification écrite sur le site Web n'a pas été mise à jour en conséquence.
EDIT: À titre de référence, le commit (à partir d'octobre 2012!) Où l'étape respective a été incluse dans l'implémentation: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362
EDIT2: Par coïncidence, j'ai trouvé ce document sur les méthodes par défaut dans hotspot qui contient une note latérale intéressante à la fin:
la source
Je vais essayer de faire valoir qu'une initialisation d'interface ne devrait pas causer d'effets secondaires de canal secondaire dont les sous-types dépendent, par conséquent, qu'il s'agisse d'un bogue ou non, ou de la manière dont Java le corrige, cela ne devrait pas avoir d'importance. l'application dans laquelle les interfaces sont initialisées.
Dans le cas d'un
class
, il est bien admis qu'il peut provoquer des effets secondaires dont dépendent les sous-classes. Par exempleToute sous-classe de
Foo
s'attend à voir 1000 $ dans la banque, n'importe où dans le code de sous-classe. Par conséquent, la superclasse est initialisée avant la sous-classe.Ne devrait-on pas faire la même chose pour les superintéfaces également? Malheureusement, l'ordre des super-interfaces n'est pas censé être significatif, il n'y a donc pas d'ordre bien défini dans lequel les initialiser.
Il vaut donc mieux ne pas établir ce genre d'effets secondaires dans les initialisations d'interface. Après tout, ce
interface
n'est pas pour ces fonctionnalités (champs / méthodes statiques) que nous empilons pour plus de commodité.Par conséquent, si nous suivons ce principe, nous ne nous soucierons pas de l'ordre dans lequel les interfaces sont initialisées.
la source