2
头图

The foundation is not strong, and the ground shakes the mountains. Hello everyone, I am the class representative.

attention: 1610751b19f537 Java class representative , get more practical dry goods.

Open a question to examine the order of code execution:

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

When new Child() is executed, what does the above code output?

I believe that many students have encountered this kind of problem, and may forget after checking the information, and the answer is still wrong when they encounter it again. The next class represents the 4 steps to take you to disassemble the execution sequence of this code, and use this to summarize the rules.

1. What is the compiler optimized?

The following two pieces of code compare the changes before and after compilation:

Child.java before 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");
    }
}

Compiled Child.class

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

Through comparison, it can be seen that the compiler moves the assignment operation of the initialization block and the instance field to before the constructor code, and retains the order of the related codes. In fact, if there are multiple constructors, the initialization code will be copied and moved over.

Based on this, the first order of priority can be derived:

  • Initialization code> Constructor code

2. What does static do?

The class loading process can be roughly divided into three stages: loading -> link -> initialization

The initialization phase can be 8 cases 1610751b19f899 Zhou Zhiming "P359 "8 Cases of Trigger Class Initialization"):

  1. When using the new keyword to instantiate an object
  2. Read or set a type of static field ( constant "))
  3. Call a static method of a type
  4. When using reflection to call a class
  5. When the class is initialized, if it is found that the parent class has not been initialized, the parent class initialization is triggered first
  6. When the virtual machine starts, it will first initialize the main class (the class that main()
  7. When the MethodHandle instance is called for the first time, initialize the class of the method pointed to by the MethodHandle.
  8. If a default method (interface method modified by default) is defined in the interface, and the implementation class of the interface is initialized, the interface must be initialized before it

The 2, 3 entries are triggered by the code static

In fact, the initialization phase is the process of executing the class constructor <clinit> method. This method is automatically generated by the compiler. It collects assignment actions and static statement blocks (static{} blocks) of all class variables modified by static The order in which the codes appear.

According to Item 5, the JVM will ensure that the <clinit> method of the parent class has been executed <clinit>

Summary about: access class variables or static methods, will trigger initialization class, and class initialization is executed <clinit> , which is executed static modified assignment action and static{} block and JVM guarantee to execute the parent class initialization, and then execute the subclass initialization .

This leads to the second order of priority:

  • Parent class static Code> subclass static Code

3. The static code is executed only once

We all know that static code (except for static methods) is executed only once.

Have you ever wondered how this mechanism is guaranteed?

The answer is: the parental delegation model.

The parent delegation model for JDK8 and before is:

Application class loader → extended class loader → start class loader

The classes written in the usual development are loaded by the application class loader by default, and it will be delegated to its parent class: the extended class loader. The extended class loader will be delegated to its parent class: the startup class loader. Only when the parent class loader reports that it cannot complete the loading request, the child loader will try to complete the loading by itself. This process is the parent delegation. The parent-child relationship of the three is not achieved through inheritance, but through the combined model.

The implementation of this process is also very simple, the key implementation code is shown below:

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

Combining the notes, I believe it is easy for everyone to understand.

The code delegated by the parents knows that under the same class loader, a class can only be loaded once, which limits it to be initialized only once. So the static code in the class (except for static methods) is only executed once when the class is initialized

4. <init> and <clinit>

The class constructor automatically generated by the compiler has been introduced before: <clinit> method, it will collect the assignment actions and static statement blocks (static{} block) of all class variables modified by static Time execution

Correspondingly, the compiler will also generate a <init> method, which will collect the assignment action of the instance field, the code in the initialization statement block ({} block) and the constructor (Constructor), and retain the order of appearance of the code. It will be displayed in the new Execute after the instruction

Therefore, when we new a class, if the JVM does not load the class, it will be initialized and then instantiated.

At this point, the third priority rule is ready to come out:

  • Static code (static() block, static field assignment statement)> initialization code (() block, instance field assignment statement)

5. Regular practice

Combining the previous three rules, sum up the following two:

1. Static code (static () block, static field assignment statement)> initialization code ({} block, instance field assignment statement)> constructor code

2. The parent class static Code> subclass static Code

According to the previous summary, the initialization code and constructor code are collected by the compiler in <init> , and the static code is collected in <clinit> , so the above rules are merged again:

parent class <clinit> > child class <clinit> > parent class <init> > child class <init>

Corresponding to the opening question, let's practice it:

When new Child() is executed, the new keyword triggers the initialization of the Child class. The JVM finds that it has a parent class. It first initializes the Parent class, starts to execute the <clinit> method of the Parent class, and then executes the <clinit> method of the Child class (remember the collection in <clinit> What?).

Then start to instantiate an object of the Child class. At this time <init> method of Child. It is found that it has a parent class. The <init> , and then the <init> child class is executed (remember <init> is collected in 0610751b19ff90? ).

I believe that seeing this, you already have an answer to the question at the beginning of the article, you might as well write the output sequence by hand first, and then write the code to verify it yourself.

Concluding remarks

static is often used in development. Every time I write, I always have two question marks in my heart. Why should I use static ? This corresponds to the first sentence of the opening:

The foundation is not strong, the ground shakes the mountains

It can be seen from this article that static is far more than just class variables, static methods are so simple. In the classic singleton mode, you will see static . The next article will write how to write a singleton mode in fancy



[Recommended reading]
RabbitMQ tutorial
Freemarker Tutorial (1)
downloaded attachment name is always garbled? You should read the RFC document!
MySQL priority queue in a simple way (the order by limit problem you will definitely step on)


The codeword is not easy, welcome to like, follow and share.
Search: [ Java class representative ], follow the official account, and get more Java dry goods in time.


Java课代表
640 声望1k 粉丝

JavaWeb一线开发,5年编程经验