Maison  >  Article  >  Java  >  Laissez-vous comprendre immédiatement le mot-clé statique en Java

Laissez-vous comprendre immédiatement le mot-clé statique en Java

醉折花枝作酒筹
醉折花枝作酒筹avant
2021-08-04 17:49:462149parcourir

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.

1. Qu'est-ce que le compilateur optimise ?

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é :

  • Code d'initialisation > Code constructeur

2.

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 classe583d030be372af71281df966e84181a5, 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é :

code statique de la classe parent > code statique
  • 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

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 :

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

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 583d030be372af71281df966e84181a5

Le constructeur de classe généré automatiquement par le compilateur a été introduit auparavant : b75c58c37951302f0f985c3101bcd2b3, 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

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

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 :

  • Code statique (bloc statique{}, instruction d'affectation de champ statique) >Code d'initialisation (bloc {}, instruction d'affectation de champ d'instance)

Pratique régulière

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.

Selon le résumé précédent, le code d'initialisation et le code du constructeur sont collectés par le compilateur dans 7e51f00a783d7eb8f68358439dee7daf, et le code statique est collecté dans 583d030be372af71281df966e84181a5, donc les règles ci-dessus sont à nouveau fusionnées :

Classe parent

583d030be372af71281df966e84181a5 > 子类583d030be372af71281df966e84181a5 > 父类 7e51f00a783d7eb8f68358439dee7daf > 子类 7e51f00a783d7eb8f68358439dee7daf

Correspondant à la question du début, pratiquons-le :

Lors de l'exécution de new Child(), le mot-clé new déclenche l'initialisation de la classe Child. Lorsque la JVM découvre qu'elle a une classe parent, elle initialise d'abord la classe Parent. et commence à exécuter la classe Parent. La méthode 583d030be372af71281df966e84181a5, puis exécute la méthode 583d030be372af71281df966e84181a5 de la classe Child (rappelez-vous ce qui est collecté dans 583d030be372af71281df966e84181a5 ?).

Ensuite, commencez à instancier un objet de la classe Child. À ce stade, nous sommes prêts à exécuter la méthode 7e51f00a783d7eb8f68358439dee7daf d'abord la classe parent, puis exécutez le 7e51f00a783d7eb8f68358439dee7daf de la classe enfant ;(Vous vous souvenez de ce qui est collecté dans 7e51f00a783d7eb8f68358439dee7daf ?).

Je pense qu'après avoir lu ceci, vous avez déjà la réponse à la question d'ouverture. Vous pourriez aussi bien écrire d'abord à la main la séquence de sortie, puis écrire le code pour le vérifier vous-même.

Conclusion

static est souvent utilisé dans le développement quotidien. Chaque fois que j'écris, j'ai toujours deux questions en tête. Pourquoi devrais-je utiliser static ?

Comme vous pouvez le voir dans cet article, l'application de la statique va bien au-delà des variables de classe et est aussi simple que les méthodes statiques. Dans le modèle singleton classique, vous verrez diverses utilisations de la statique. Le prochain article expliquera comment écrire le modèle singleton de manière sophistiquée.

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!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer