Lorsque vous envisagez l'initialisation de la classe, nous savons tous que lors de l'initialisation de la sous-classe, si la classe parent n'est pas initialisée, la sous-classe doit être initialisée en premier. Cependant, les choses ne sont pas aussi simples qu'une seule phrase.
Tout d'abord, jetons un coup d'œil aux conditions de déclenchement d'initialisation en Java:
(1) Lorsque vous utilisez des objets nouveaux sur instanciés et accédez aux données et méthodes statiques, c'est-à-dire lors de la rencontre d'instructions: Nouveau, getStatic / PutStatic et invokestatique;
(2) lors de l'utilisation de la réflexion pour appeler la classe;
(3) Lors de l'initialisation d'une classe, si la classe parent n'a pas été initialisée, l'initialisation de la classe parent sera déclenchée en premier;
(4) La classe où la méthode principale de l'entrée est exécutée est située;
(5) La classe où la poignée de la méthode est située dans la prise en charge du langage dynamique de JDK1.7, si l'initialisation n'est pas déclenchée;
Après compilation, une méthode <clinit> est générée et l'initialisation de la classe est réalisée dans cette méthode. Cette méthode est uniquement exécutée, et le JVM en assure et effectue un contrôle de synchronisation;
Parmi eux, la condition (3), du point de vue de l'appel de méthode, est la sous-classe <clinit> qui appelle récursivement la classe parent <clinit> au début, ce qui est similaire au fait que nous devons d'abord appeler le constructeur de classe parent dans le constructeur de sous-classe;
Mais il convient de noter que le "déclenchement" ne termine pas l'initialisation, ce qui signifie qu'il est possible que l'initialisation de la sous-classe se termine à l'avance avant l'initialisation de la classe parent, où se trouve le "danger".
1. Un exemple d'initialisation de classe:
Dans cet exemple, j'utilise une classe périphérique pour contenir 2 classes de membres statiques avec des relations d'héritage. Parce que l'initialisation de la classe périphérique et des classes de membres statiques n'a aucune relation causale, il est sûr et pratique de le montrer comme ça;
La classe A et Child Class B contiennent respectivement les fonctions principales. D'après la condition de déclenchement ci-dessus (4), on peut voir que différents chemins d'initialisation de classe sont déclenchés en appelant respectivement ces deux fonctions principales;
Le problème avec cet exemple est que la classe parent contient la référence statique de la classe enfant et l'initialise à la définition:
classe publique WapperClass {classe statique privée A {static {System.out.println ("class a initialisation start ..."); } // La classe Parent contient des références statiques de la classe enfant privée statique b b = new b (); Int statique protégé = 9; statique {System.out.println ("Classe A Initialisation End ..."); } public static void main (String [] args) {}} La classe statique privée B étend un {statique {System.out.println ("Classe B Initialisation Démarrer ..."); } // Le domaine de la sous-classe dépend du domaine de la classe parent privée static int bint = 9 + a.aint; public b () {// Le domaine statique du constructeur dépend de la classe System.out.println ("Call du constructeur pour la classe B" + "Valeur de Bint" + Bint); } statique {System.out.println ("La classe B L'initialisation se termine ..." + "Valeur de Aint:" + bint); } public static void main (String [] args) {}}} Scénario 1: le résultat de sortie lorsque l'entrée est une fonction principale de la classe B:
/ ** * L'initialisation de la classe A démarre ... * Le constructeur de la classe B appelle la valeur de Bint 0 * La classe A s'est terminée ... * L'initialisation de la classe B démarre ... * La classe B se termine ... une valeur non: 18 * /
Analyse: On peut voir que l'appel de la fonction principale déclenche l'initialisation de la classe B et entre dans la méthode <clinit> de classe B. classe A, car sa classe parentale, commence à initialiser en premier et entre dans la méthode <clinit> de A, et il y a une déclaration nouvelle B (); À l'heure actuelle, B sera instancié, ce qui est déjà dans la <clinit> de la classe B. Le thread principal a obtenu le verrou et a commencé à exécuter le <clinit> de la classe B. Nous avons dit au début que le JVM garantira que la méthode d'initialisation d'une classe n'est exécutée qu'une seule fois. Après avoir reçu la nouvelle instruction, le JVM n'entrera pas à nouveau la méthode <clinit> de la classe B mais sera instancié directement. Cependant, pour le moment, la classe B n'a pas terminé l'initialisation de la classe, vous pouvez donc voir que la valeur de Bint est 0 (ce 0 est l'initialisation zéro effectuée après avoir alloué la mémoire de la zone de méthode pendant l'étape de préparation du chargement des classes);
Par conséquent, on peut conclure que la classe parent contient le domaine statique du type d'enfant et effectue l'action d'affectation, ce qui peut entraîner la réalisation de l'instanciation de la sous-classe avant la fin de l'initialisation de la classe;
Scénario 2: Résultat de sortie lorsque l'entrée est une fonction principale de la classe A:
/ ** * L'initialisation de la classe A démarre ... * L'initialisation de la classe B démarre ... * La classe B s'est terminée ... La valeur de Aint: 9 * Le constructeur de la classe B appelle la valeur de Bint 9 * L'initialisation de la classe A se termine ... * / /
Analyse: Après l'analyse du scénario 1, nous savons que le déclenchement de l'initialisation de la classe A par l'initialisation de la classe B entraînera la réalisation de l'instanciation de la variable de classe B dans la classe A avant la fin de l'initialisation de la classe B. Donc, si la classe A est d'abord initialisée, la classe B peut-elle être déclenchée en premier lors de l'instanciation de la variable de classe, de sorte que l'initialisation est effectuée avant l'instanciation? La réponse est oui, mais il y a encore des problèmes.
Selon la sortie, nous pouvons voir que l'initialisation de la classe B est effectuée avant l'initialisation de la classe A terminée, ce qui provoque des variables telles que la variable de classe ne soit pas initialisée seulement après la classe B est initialisée, de sorte que la valeur de nous attendre par le domaine dans la classe B est "0", plutôt que "18" comme nous l'avons attendu;
Conclusion: En résumé, on peut conclure qu'il est très dangereux d'inclure des variables de classe de types de sous-classe dans la classe parent et de les instancier lors de leur définition. La situation spécifique peut ne pas être aussi simple qu'un exemple. L'appel d'appel pour attribuer des valeurs à la définition est également dangereux. Même si vous souhaitez inclure des domaines statiques de types de sous-classe, vous devez également attribuer des valeurs via des méthodes statiques, car le JVM peut vous assurer que toutes les actions d'initialisation sont terminées avant que la méthode statique ne soit appelée (bien sûr, cette garantie est que vous ne devez pas inclure statique B B = nouveau B (); un tel comportement d'initialisation);
2. Un exemple instancié:
Tout d'abord, vous devez connaître le processus de création d'objets:
(1) Lorsque vous rencontrez une nouvelle instruction, vérifiez si la classe a terminé le chargement, la vérification, la préparation, l'analyse et l'initialisation (le processus d'analyse consiste à analyser la référence du symbole dans une référence directe, telle que le nom de la méthode est une référence de symbole, qui peut être effectuée lors de l'utilisation de cette référence de symbole une fois l'initialisation terminée, précisément pour prendre en charge la liaison dynamique), ces processus sont effectués.
(2) allouer la mémoire, utiliser la méthode de la liste gratuite ou de la collision de pointeur et "zéro" la mémoire nouvellement allouée. Par conséquent, toutes les variables d'instance sont initialisées à 0 par défaut (référencées en tant que null) dans ce lien;
(3) Exécuter la méthode <init>, notamment la vérification de l'appel vers la méthode <it init> (constructeur) de la classe parent, les actions d'attribution définies par la variable d'instance, l'instanciateur s'exécute dans l'instanceur et enfin appelant les actions dans le constructeur.
Cet exemple peut être plus connu, c'est-à-dire qu'il viole "n'appelez pas les méthodes remplacées dans le constructeur, la méthode de clone et la méthode ReadObject". La raison en est que le polymorphisme en Java, c'est-à-dire la liaison dynamique.
Le constructeur de la classe A Parent A contient une méthode protégée et la classe B est sa sous-classe.
classe publique TrustStantiation {classe statique privée A {public a () {DoSomething (); } protégé void dosomething () {System.out.println ("a's Dosomething"); }} La classe statique privée B étend un {private int bint = 9; @Override Protected void DoSomething () {System.out.println ("B's Dosomething, bint:" + bint); }} public static void main (String [] args) {b b = new b (); }}Résultat de sortie:
/ ** * B's Dosomething, bint: 0 * /
Analyse: Tout d'abord, vous devez savoir que lorsqu'il n'y a pas d'affichage, le compilateur Java générera le constructeur par défaut et appellera le constructeur de la classe parent au début. Par conséquent, le constructeur de la classe B appellera le constructeur de la classe A premier au début.
La méthode protégée que la méthode est appelée dans la classe A. À partir du résultat de sortie, nous voyons que l'implémentation de la méthode de la sous-classe est réellement appelée, et que l'instanciation de la sous-classe n'a pas encore commencé, donc Bint n'est pas 9 comme "attendu" mais 0;
Cela est dû à la liaison dynamique, Dosomething est une méthode protégée, il est donc appelé via la directive InvokeVirtual, qui trouve l'implémentation de la méthode correspondante basée sur le type d'instance d'objet (voici l'objet d'instance de B, et la méthode correspondante est l'implémentation de la méthode de la classe B), donc ce résultat est.
Conclusion: Comme mentionné précédemment, "n'appelez pas les méthodes remplacées dans le constructeur, la méthode de clone et la méthode readObject".
Ce qui précède sont les deux "champs de mines" dans l'initialisation de la classe Java et l'instanciation qui vous sont présentés. J'espère que cela sera utile à l'apprentissage de tous.