• 一文以概之:Java类成员加载顺序


    1. 导入

    最近在阅读Thinking in Java,谈到Java类成员加载顺序一部分,不由想起以前被大学老师支配的恐惧,但是翻看此书之后,有点豁然开朗之感,遂起心思,想要写一篇文章,详细分析Java类成员加载顺序,希望能够帮助新人朋友们,如有失误之处,劳请指出,感激不尽。

    2. 分析之前

    在开始长篇大论之前,我想先写一点简单但需要注意的知识:

    2.1 变量定义的顺序决定了初始化的顺序

    咱先举一个🌰,我喜欢🐕,咱们先写一个阿拉斯加吧。

    import java.util.*;
    
    /**
     * @author LKQ
     * @date 2022/7/28 19:07
     * @description
     */
    public class Alaska {
        private static int i = print("大白");
        private static int j = print("小黄");
        static int print(String s) {
            System.out.println(s);
            return 0;
        }
        public static void main(String[] args) {
           	
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Output:
    大白
    小黄

    public class Alaska {
        private static int j = print("小黄");
        private static int i = print("大白");
        static int print(String s) {
            System.out.println(s);
            return 0;
        }
        public static void main(String[] args) {
           	
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Output:
    小黄
    大白

    从上面可以看出,大白i小黄j两个变量,定义的顺序不同,输出的结果顺序也不同。

    请注意,虽然main()啥也没干,但是会有输出。


    2.2 类中每个成员都有默认值

    public class Alaska {
        private static boolean isSick;
        private static int age;
        private static String name;
        private static double weight;
        public static void main(String[] args) {
           	System.out.println("Data type: Initial Value");
            System.out.println("boolean: " + isSick);
            System.out.println("int: " + age);
            System.out.println("String: " + name);
            System.out.println("double: " + weight);
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Output:
    Data type: Initial Value
    boolean: false
    int: 0
    String: null
    double: 0.0

    可以看到,我们并没有给这个变量赋值,但依然有其对应的默认值。


    2.3 静态初始化只执行一次,且在这个类最开始被使用的时候

    public class Alaska {
        
        private static int i = 10;
        static {
            System.out.println("Alaska static block initialized");
        }
        static class AlaskaInner {
            AlaskaInner() {
                System.out.println("static class AlaskaInner initialized");
            }
            static {
                System.out.println("static class AlaskaInner static initialized");
            }
        }
        private static String name = getName();
        {
            System.out.println("Alaska non-static block initialized");
        }
        private static String getName() {
            System.out.println("alaska - 阿拉斯加");
            return "alaska";
        }
        Alaska() {
            System.out.println("Alaska constructor");
        }
        public static void main(String[] args) {
            System.out.println("create a new Alaska");
            Alaska a = new Alaska();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Output:
    Alaska static block initialized
    alaska - 阿拉斯加
    create a new Alaska
    Alaska non-static block initialized
    Alaska constructor

    假设我们养两只阿拉,会发生什么呢?

    public class Alaska {
        
        private static int i = 10;
        static {
            System.out.println("Alaska static block initialized");
        }
        static class AlaskaInner {
            AlaskaInner() {
                System.out.println("static class AlaskaInner initialized");
            }
            static {
                System.out.println("static class AlaskaInner static initialized");
            }
        }
        private static String name = getName();
        {
            System.out.println("Alaska non-static block initialized");
        }
        private static String getName() {
            System.out.println("alaska - 阿拉斯加");
            return "alaska";
        }
        Alaska() {
            System.out.println("Alaska constructor");
        }
        public static void main(String[] args) {
            System.out.println("create a new Alaska");
            Alaska a = new Alaska();
            Alaska a1 = new Alaska();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    Output:
    Alaska static block initialized
    alaska - 阿拉斯加
    create a new Alaska
    Alaska non-static block initialized
    Alaska constructor
    Alaska non-static block initialized
    Alaska constructor

    可以看到,静态的初始化,不管生成多少只阿拉,它只会执行一次

    非静态初始化以及构造器,每 new Alaska()都执行了一次。

    注意:静态内部类并没有初始化。只用在调用了静态内部类时,才会进行初始化。

        public static void main(String[] args) {
            System.out.println("create a new Alaska");
            Alaska a = new Alaska();
            Alaska a1 = new Alaska();
            AlaskaInner alaskaInner = new AlaskaInner();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Output:
    Alaska static block initialized
    alaska - 阿拉斯加
    create a new Dog
    Alaska non-static block initialized
    Alaska constructor
    Alaska non-static block initialized
    Alaska constructor
    static class AlaskaInner static initialized
    static class AlaskaInner initialized

    如果一只不养但是看到别人养了呢?

    public class Alaska {
        
        private static int i = 10;
        static {
            System.out.println("Alaska static block initialized");
        }
        static class AlaskaInner {
            AlaskaInner() {
                System.out.println("static class AlaskaInner initialized");
            }
            static {
                System.out.println("static class AlaskaInner static initialized");
            }
        }
        private static String name = getName();
        {
            System.out.println("Alaska non-static block initialized");
        }
        private static String getName() {
            System.out.println("alaska - 阿拉斯加");
            return "alaska";
        }
        Alaska() {
            System.out.println("Alaska constructor");
        }
    }
    class Dog {
        public static void main(String[] args) {
            System.out.println("create a new dog");
            Alaska.getName();
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    Output:
    create a new dog
    Alaska static block initialized
    alaska - 阿拉斯加
    alaska - 阿拉斯加

    如果不喜欢🐶,也没看到别人家的狗。

    class Dog {
        public static void main(String[] args) {
            // Alaska a = new Alaska();
            // Alaska a1 = new Alaska();
            System.out.println("我不喜欢阿拉");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Output:
    我不喜欢阿拉

    那么,是否可以得出这样的结论:静态初始化,只执行一次:当首次生成这个类的对象,或者首次访问属于那个类的静态数据成员时(即使从未生成过那个类的对象)请好好琢磨

    3. 步入正题

    还是以上面的阿拉为例,现在我们清除不必要的东西,只保留部分内容,并进行适当的扩展。一步一步来分析。

    import java.util.*;
    
    /**
     * @author LKQ
     * @date 2022/7/28 19:07
     * @description
     */
    public class Alaska extends Dog{
    	static {
            System.out.println("Alaska static initialized");
        }
        public static void main(String[] args) {
            System.out.println("create a new Alaska");
        }
    }
    class Dog extends Animal{
    	static {
            System.out.println("Dog static initialized");
        }
    }
    class Animal {
        static {
            System.out.println("Animal static initialized");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    Output:
    Animal static initialized
    Dog static initialized
    Alaska static initialized
    create a new Alaska

    如果不让 Alaska extends Dog

    Output:
    Alaska static initialized
    create a new Alaska

    Alaska运行Java时,所发生的第一件事情就是试图访问Alaska.main()(一个静态方法)。于是加载器开始启动,并找出Alaska类的编译代码(Alaska.class文件中)。加载过程中,编译器注意到它有一个基类Dog (由extends得知),于是它继续加载Dog不管有没有产生一个该基类的对象,这都要发生。

    如果该基类还有其自身的基类,那么第二个基类就会被加载,如此类推。 自底向上找到根基类后Animal,然后自顶向下执行static初始化。这其实就是Java类加载时的双亲委派模型机制。

    完成类加载后,对象就可以被创建了。2.2说过,对象中的所有基本类型都被设为默认值,对象引用被设为null。这是通过将对象内存设为二进制零值而一举生成的。

    那么实例变量和构造器谁先加载呢?

    import java.util.*;
    
    /**
     * @author LKQ
     * @date 2022/7/28 19:07
     * @description
     */
    public class Alaska extends Dog{
        private static int i = 10;
        public int d = print();
        static {
            System.out.println("Alaska static initialized");
        }
        Alaska() {
            System.out.println("Alaska constructor");
        }
        public static void main(String[] args) {
            System.out.println("create a new Alaska");
            Alaska alaska = new Alaska();
        }
    }
    class Dog extends Animal{
        private static int j = 5;
        public int d = print();
        static {
            System.out.println("Dog static initialized");
        }
        Dog() {
            System.out.println("Dog constructor ");
        }
    }
    class Animal {
        private static int k = 0;
        private int a = print();
        static {
            System.out.println("Animal static initialized");
        }
        int print() {
            System.out.println("Animal instance variable initialized " + k + " time");
            k++;
            return k;
        }
        Animal() {
            System.out.println("Animal constructor");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    Animal static initialized
    Dog static initialized
    Alaska static initialized
    create a new Alaska
    Animal instance variable initialized 0 time
    Animal constructor
    Animal instance variable initialized 1 time
    Dog constructor
    Animal instance variable initialized 2 time
    Alaska constructor

        // 如果这里修改为如下代码
    	public static void main(String[] args) {
            System.out.println("create a new Dog");
            Dog dog = new Dog();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Output:
    Animal static initialized
    Dog static initialized
    Alaska static initialized
    create a new Dog
    Animal instance variable initialized 0 time
    Animal constructor
    Animal instance variable initialized 1 time
    Dog constructor

    可以看到,首先是static初始化,从父类到子类输出。然后new Alaska()时,根父类实例变量 a 先初始化,然后调用其构造器完成初始化,顺序也是从根父类到子类。

    得出结论:实例变量 > 构造器

    那加入非静态初始化块呢?它应该出现在哪?

    import java.util.*;
    
    /**
     * @author LKQ
     * @date 2022/7/28 19:07
     * @description
     */
    public class Alaska extends Dog{
        private static int i = 10;
        public int d = print();
    
        static {
            System.out.println("Alaska static block initialized");
        }
        {
            System.out.println("Alaska non-static block initialized");
        }
        Alaska() {
            System.out.println("Alaska constructor");
        }
    	
        public static void main(String[] args) {
            System.out.println("create a new Alaska");
            Alaska a = new Alaska();
        }
    }
    class Dog extends Animal{
        private static int j = 5;
        public int d = print();
        static {
            System.out.println("Dog static initialized");
        }
        {
            System.out.println("Dog non-static block initialized");
        }
        Dog() {
            System.out.println("Dog constructor ");
        }
    }
    class Animal {
        private static int k = 0;
        private int a = print();
        static {
            System.out.println("Animal static initialized");
        }
        {
            System.out.println("Animal non-static block initialized");
        }
        int print() {
            System.out.println("Animal instance variable initialized " + k + " time");
            k++;
            return k;
        }
        Animal() {
            System.out.println("Animal constructor");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    Output:
    Animal static initialized
    Dog static initialized
    Alaska static block initialized
    create a new Alaska
    Animal instance variable initialized 0 time
    Animal non-static block initialized
    Animal constructor
    Animal instance variable initialized 1 time
    Dog non-static block initialized
    Dog constructor
    Animal instance variable initialized 2 time
    Alaska non-static block initialized
    Alaska constructor

    可以看到,非静态初始块顺序 出现在 实例变量构造器 之间。

    得出结论:实例变量 > 非静态初始块 > 构造器

    4. 总结

    借鉴Thinking in Java来下个结论吧。假设有一个Dog类:

    1. 即使没有显示地使用static关键字,构造器实际上也是静态方法。因此,当首次创建类型为Dog的对象时(构造器可以看成静态方法),或者Dog类的静态方法/静态域首次被访问时,Java解释器必须查找类路径,以定位Dog.class文件。如果有基类存在,那么继续往上定位
    2. 载入Dog.class,这将创建一个Class对象,有关静态初始化的所有动作都会执行。因此,静态初始化只在Class对象首次加载的时候进行一次。
    3. 当用 new Dog()创建对象时,首先在堆上为Dog对象分配足够的存储空间。
    4. 这块存储空间会被清零,这就自动地将Dog对象中的所有基本类型数据都设置成了默认值,而引用类型则被设置为null
    5. 从根基类开始,每创建一个对象,依次初始化 实例变量非静态初始块构造器,然后到子类。
  • 相关阅读:
    【华为OD机试真题 python】最长的顺子【2022 Q4 | 200分】
    5个前端练手项目(html css js canvas)
    敏捷发布列车初探3 ---- Agile Release Train
    高压电气系统验证
    Python中的%3d %9.2f %02d %-*s %*s %.*s解释
    开始MySQL之路——MySQL存储引擎概念
    初探动态规划
    centos7 安装 superset 2.0 并安装 pg mysql等驱动
    ModuleNotFoundError: No module named ‘xxx‘
    Python交互Redis
  • 原文地址:https://blog.csdn.net/wohuizuofan1/article/details/126053003