Je crois que de nombreux étudiants ont rencontré ce genre de question. Ils ont peut-être vérifié l'information puis l'ont oubliée. S'ils la rencontrent à nouveau, ils ne peuvent toujours pas y répondre correctement. Ensuite, à travers 4 étapes, je vais vous amener à démonter la séquence d'exécution de ce code et à résumer les règles.
Une question d'ouverture pour examiner la séquence d'exécution du code :
public class Parent { static { System.out.println("Parent static initial block"); } { System.out.println("Parent initial block"); } public Parent() { System.out.println("Parent constructor block"); } } public class Child extends Parent { static { System.out.println("Child static initial block"); } { System.out.println("Child initial block"); } private Hobby hobby = new Hobby(); public Child() { System.out.println("Child constructor block"); } } public class Hobby { static{ System.out.println("Hobby static initial block"); } public Hobby() { System.out.println("hobby constructor block"); } }
Que produit le code ci-dessus lorsque new Child() est exécuté ?
Je crois que de nombreux étudiants ont rencontré ce genre de problème. Ils ont peut-être vérifié l'information puis l'ont oubliée. S'ils la rencontrent à nouveau, ils ne peuvent toujours pas y répondre correctement. Ensuite, le représentant de la classe vous fera suivre 4 étapes pour démonter la séquence d'exécution de ce code et résumer les règles.
Les deux morceaux de code suivants comparent les modifications avant et après la compilation :
Child.java avant la compilation
public class Child extends Parent { static { System.out.println("Child static initial block"); } { System.out.println("Child initial block"); } private Hobby hobby = new Hobby(); public Child() { System.out.println("Child constructor block"); } }
Child.class après la compilation
public class Child extends Parent { private Hobby hobby; public Child() { System.out.println("Child initial block"); this.hobby = new Hobby(); System.out.println("Child constructor block"); } static { System.out.println("Child static initial block"); } }
Vous pouvez voir par comparaison que le compilateur attribue des valeurs aux blocs d'initialisation et champs d'instance, déplacés avant le code constructeur, et l'ordre des codes associés est conservé. En fait, s'il existe plusieurs constructeurs, le code d'initialisation sera copié et déplacé.
Sur cette base, nous pouvons tracer le premier ordre de priorité :
Le processus de chargement d'une classe peut être grossièrement divisé en trois étapes : Chargement-> Lien-> Initialisation
La phase d'initialisation peut être déclenchée par 8 situations Zhou Zhiming》P359 "8 situations qui déclenchent l'initialisation de la classe") :
Lors de l'instanciation d'un objet à l'aide du nouveau mot-clé
Lecture ou définition de champs statiques d'un type (sauf constante "))
Appel d'une méthode statique d'un type
Lors de l'appel d'une classe en utilisant réflexion
Lors de l'initialisation d'une classe, s'il s'avère que la classe parent n'a pas été initialisée, l'initialisation de sa classe parent sera déclenchée en premier
Au démarrage de la machine virtuelle, la classe principale (la classe contenant la méthode main()) sera initialisée en premier.
Lorsque l'instance MethodHandle est appelée pour la première fois, la classe où la méthode pointée par MethodHandle est initialisée
S'il s'agit d'une méthode par défaut (modifiée par défaut. méthode d'interface) est définie dans l'interface, la classe d'implémentation de l'interface est initialisée. L'interface doit être initialisée avant
Les éléments 2 et 3 sont déclenchés par du code statique
En fait, la phase d'initialisation est le processus d'exécution. la méthode constructeur de classe<clinit>, qui est automatiquement générée par le compilateur. , qui collecte les actions d'affectation et les blocs d'instructions statiques (blocs static{}) de toutes les variables de classe modifiées de manière statique, et conserve l'ordre dans lequel ces codes apparaissent.
Selon le point 5, la JVM veillera à ce que le Cela conduit au deuxième ordre de priorité : Avez-vous déjà réfléchi à la façon dont ce mécanisme est garanti La réponse est : le modèle de délégation parentale dans JDK8 et avant ? : Chargeur de classe d'application → Chargeur de classe d'extension → Chargeur de classe de démarrage Les classes écrites en développement quotidien sont chargées par les classes d'application par défaut Lorsque le chargeur de classe parent est chargé, il déléguera à sa classe parent : le chargeur de classe d'extension, et le chargeur de classe d'extension déléguera à sa classe parent : le chargeur de classe de démarrage. Ce n'est que lorsque les commentaires du chargeur de classe parent ne peuvent pas terminer la demande de chargement que le serveur essaiera de terminer le chargement par lui-même. La relation parent-enfant entre les trois n'est pas obtenue par héritage, mais par le mode combinaison. La mise en œuvre de ce processus est également très simple. Le code de mise en œuvre clé est présenté ci-dessous : Combiné avec les commentaires, je crois. c'est facile à comprendre pour tout le monde. Il ressort du code délégué par les parents que sous le même chargeur de classe, une classe ne peut être chargée qu'une seule fois, ce qui la limite à n'être initialisée qu'une seule fois. Par conséquent, le code statique de la classe (à l'exception des méthodes statiques) n'est exécuté qu'une seule fois lors de l'initialisation de la classe 4 et <clinit> Le constructeur de classe généré automatiquement par le compilateur a été introduit auparavant : < clinit>, il collectera les actions d'affectation et les blocs d'instructions statiques (blocs static{}) de toutes les variables de classe modifiées de manière statique et conservera l'ordre d'apparition du code. Il sera exécuté lors de l'initialisation de la classe Ainsi, lorsque nous créons une nouvelle classe, si la JVM n'a pas chargé la classe, elle sera d'abord initialisée puis instanciée. À ce stade, la troisième règle de priorité est prête à sortir : Combiner les trois règles précédentes et résumer les deux suivantes: 1. Code statique (bloc statique {}, instruction d'affectation de champ statique) > Code d'initialisation (bloc {}, instruction d'affectation de champ d'instance) > Code constructeur 2. Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!3.le code statique n'est exécuté qu'une seule fois
Nous. tout le monde sait que le code statique (sauf les méthodes statiques) n'est exécuté qu'une seule fois
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 首先检查该类是否被加载过
// 如果加载过,直接返回该类
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类抛出ClassNotFoundException
// 说明父类无法完成加载请求
}
if (c == null) {
// 如果父类无法加载,转由子类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
En conséquence, le compilateur. générera également une méthode < ;init>, elle collectera l'action d'affectation du champ d'instance, le code dans le bloc d'instruction d'initialisation (bloc {}) et le constructeur (Constructor), et conservera l'ordre d'apparition du code. Elle sera exécutée après la nouvelle instruction
Pratique régulière
<clinit>
> 子类<clinit>
> 父类 <init>
> 子类 <init>