一文以概之: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) {
       	
    }
}

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) {
       	
    }
}

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


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

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

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

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


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("我不喜欢阿拉");
    }
}

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

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

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

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

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. 从根基类开始,每创建一个对象,依次初始化 实例变量非静态初始块构造器,然后到子类。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值