Ordre Java d’initialisation et d’instanciation

J’essaie de reconstituer le processus d’initialisation et d’instanciation dans la JVM, mais le JLS est un peu obtus sur quelques détails. C’est ce que j’ai pu comprendre jusqu’à présent.

Initialisation

  1. Récursivement Initialise les variables finales statiques de la classe et ses interfaces sont des constantes de temps de compilation.

  2. Retour à la récursivité, traitement des blocs statiques et des champs statiques dans l’ordre textuel.

Instanciation

  1. Récursivement Initialise les variables d’instance finales de la classe qui sont des constantes de temps de compilation.

  2. Retour à la récursivité dans le traitement des blocs non statiques et des champs d’instance dans un ordre textuel en les ajoutant aux constructeurs à mesure qu’ils reviennent.


Ok, alors maintenant pour les questions.

  1. les interfaces sont-elles traitées par ordre de déclaration?

  2. les interfaces sont-elles traitées dans une stack récursive distincte?

    a) si oui, les interfaces sont-elles traitées avant ou après les super-classes?

    b) Si oui, ai-je raison de déduire que l’un ou l’autre (Interface ou Superclass) obtient ses champs de constantes non-compilation à l’initialisation avant les autres constantes de compilation.

  3. Quel rôle jouent les appels au constructeur non par défaut super () dans ce processus?

  4. Est-ce que je me trompe dans l’une de mes conclusions?

  5. Est-ce que je manque d’autres détails importants?

Il est important de faire la distinction entre l’initialisation d’une classe et l’initialisation d’un object.

Initialisation de classe

Une classe ou une interface est initialisée lors du premier access , en atsortingbuant les champs de constante de compilation, puis en initialisant récursivement la super-classe (si elle n’a pas déjà été initialisée), puis en traitant les initialiseurs statiques (pour les champs statiques constantes).

Comme vous l’avez remarqué, l’initialisation d’une classe ne déclenche pas à elle seule l’initialisation des interfaces qu’elle implémente. Les interfaces sont donc initialisées lorsqu’elles sont utilisées pour la première fois, généralement en lisant un champ qui n’est pas une constante de compilation . Cet access peut se produire lors de l’évaluation d’un initialiseur, provoquant une initialisation récursive.

Il est également intéressant de noter que l’initialisation n’est pas déclenchée par l’access à des champs qui sont des constantes de temps de compilation, car ceux-ci sont évalués au moment de la compilation :

Une référence à un champ qui est une variable constante (§4.12.4) doit être résolue au moment de la compilation à la valeur V désignée par l’initialiseur de la variable constante.

Si un tel champ est statique, aucune référence au champ ne doit être présente dans le code d’un fichier binary, y compris la classe ou l’interface qui a déclaré le champ. Un tel champ doit toujours sembler avoir été initialisé (§12.4.2); la valeur initiale par défaut du champ (si différente de V) ne doit jamais être respectée.

Si un tel champ n’est pas statique, aucune référence au champ ne doit être présente dans le code d’un fichier binary, sauf dans la classe contenant le champ. (Ce sera une classe plutôt qu’une interface, car une interface ne contient que des champs statiques.) La classe doit avoir du code pour définir la valeur du champ sur V lors de la création de l’instance (§12.5).

Initialisation d’object

Un object est initialisé chaque fois qu’un nouvel object est créé , généralement en évaluant une expression de création d’instance de classe. Cela se déroule comme suit:

  1. Affectez les arguments du constructeur aux variables de paramètre nouvellement créées pour cet appel de constructeur.

  2. Si ce constructeur commence par une invocation explicite du constructeur (§8.8.7.1) d’un autre constructeur de la même classe (en utilisant ceci), alors évaluez les arguments et traitez cette invocation de constructeur récursivement en utilisant ces mêmes cinq étapes. Si l’invocation de ce constructeur se termine brusquement, cette procédure se termine brusquement pour la même raison. sinon, passez à l’étape 5.

  3. Ce constructeur ne commence pas par un appel explicite de constructeur d’un autre constructeur de la même classe (utilisant ceci). Si ce constructeur est pour une classe autre que Object, alors ce constructeur commencera par une invocation explicite ou implicite d’un constructeur de super-classe (en utilisant super). Évaluez les arguments et traitez cette invocation du constructeur de la superclasse de manière récursive en utilisant les mêmes cinq étapes. Si l’invocation de ce constructeur se termine brusquement, cette procédure se termine brusquement pour la même raison. Sinon, passez à l’étape 4.

  4. Exécutez les initialiseurs d’instance et les initialiseurs de variables d’instance pour cette classe, en affectant les valeurs des initialiseurs de variables d’instance aux variables d’instance correspondantes, dans l’ordre de gauche à droite dans lequel ils apparaissent textuellement dans le code source de la classe. Si l’exécution de l’un de ces initialiseurs entraîne une exception, aucun autre initialiseur n’est traité et cette procédure se termine brusquement avec la même exception. Sinon, passez à l’étape 5.

  5. Exécutez le rest du corps de ce constructeur. Si cette exécution se termine brusquement, cette procédure se termine brusquement pour la même raison. Sinon, cette procédure se termine normalement.

Comme nous pouvons le voir à l’étape 3, la présence d’un appel explicite au super constructeur change simplement le constructeur de la super classe qui est appelé.

Voici un exemple qui imprime l’ordre de chaque étape pendant la création de l’object.

InstanceCreateStepTest.java:

 import javax.annotation.PostConstruct; /** * Test steps of instance creation. * * @author eric * @date Jan 7, 2018 3:31:12 AM */ public class InstanceCreateStepTest { public static void main(Ssortingng[] args) { new Sub().hello(); System.out.printf("%s\n", "------------"); new Sub().hello(); } } class Base { static { System.out.printf("%s - %s - %s\n", "base", "static", "block"); } { System.out.printf("%s - %s - %s\n", "base", "instance", "block"); } public Base() { System.out.printf("%s - %s\n", "base", "constructor"); } @PostConstruct public void init() { System.out.printf("%s - %s\n", "base", "PostConstruct"); } public void hello() { System.out.printf("%s - %s\n", "base", "method"); } } class Sub extends Base { static { System.out.printf("%s - %s - %s\n", "sub", "static", "block"); } { System.out.printf("%s - %s - %s\n", "sub", "instance", "block"); } public Sub() { System.out.printf("%s - %s\n", "sub", "constructor"); } @PostConstruct public void init() { System.out.printf("%s - %s\n", "sub", "PostConstruct"); } @Override public void hello() { // super.hello(); System.out.printf("%s - %s\n", "sub", "method"); } } 

Exécution:

Invoquez simplement la méthode principale, puis vérifiez la sortie.

Conseils:

  • Les méthodes marquées par @PostConstruct ne seront pas invoquées, sauf si vous les appelez dans un conteneur, comme Spring-boot , car l’implémentation d’une annotation comme @PostConstruct dépend de ces conteneurs.