谈谈对于类的初始化顺序理解

提到类的初始化顺序,首先我们应该思考一下类为什么要初始化呢?不初始化行不行?

不用想答案也是否定的,为什么?就比如我们需要使用某个东西,但是前提是这件东西得首先是存在的吧,这也是为什么必须初始化的原因。我们都知道局部变量需要我们手动给它赋值,而成员变量是JVM自动帮我们赋予默认值的,这也是Java保证了使用前必须初始化的一种机制。

首先我们把类的属性分为静态和非静态两种,然后分别去分析这两种更容易让人理解。

静态成员包括静态成员变量和静态代码块,静态资源是属于类的,随着类的加载而首先被加载,这也是为什么我们可以直接使用类名点的形式去使用它,而且它们两个在类的加载中会按照自然顺序加载,而且仅仅只加载一次。

非静态成员也包括普通成员变量和普通代码块,一般会在生成实例的时候去加载,也是按照自然顺序加载,每次生成一个实例都会加载一遍。

请注意我这里说成员的时候并没有提到方法,方法是不会初始化的,它只会在我们实际调用的时候才去执行相应的逻辑。

接下来看代码:

public class Person {

    static {
        System.out.println("value1: " + Person.value1);
        System.out.println("value2: " + Person.value2);
    }


    private String name = "ma";
    private Integer age = 18;


    public Person(){
        value1++;
        value2++;
    }

    {
        System.out.println("name: " + name);
        System.out.println("age: " + age);
        System.out.println("value1: " + value1);
        System.out.println("value2: " + value2);
    }

    public static int value1 = 1;
    public static int value2 = 2;

    public static void method(){
        System.out.println("method");
    }

}

假设我们仅仅通过类名点的方式访问静态成员,那么这个类仅仅只会加载静态成员,而且只会加载一次。

如果调用Person.value1,那么此时会输出:

 请注意我的静态代码块是放在最前面,它是在静态成员赋值之前的,是这样写的:

static {
    System.out.println("value1: " + Person.value1);
    System.out.println("value2: " + Person.value2);
}

假设我们把Person.value1换成value1,是直接报错的,从这里就可以看出完全按照顺序执行的初始化。而为什么Person.value1却可以呢?其实类的加载过程可以细分成 加载,连接(验证,准备,解析),初始化,使用,卸载这5个过程,而准备阶段主要为静态变量在方法区分配内存,并设置默认初始值,这也是为什么Person.value1可以这样使用而且输出为0的原因。

而如果我们把 

public static int value1 = 1;
public static int value2 = 2;

放在类的最前面,那么此时就把静态变量赋值并初始化了,我们就可以直接使用value1,而且此时value1的值也变成了1。

接下来我们看实例化也就是通常new的方式创建对象的加载顺序,如果仅仅 new Person(),此时会输出:

同样的也是先安装自然顺序加载静态成员变量或静态代码块,但是在调用构造函数之前会按照自然顺序加载普通成员变量或普通代码块,最后再执行构造函数。也就是说实际上构造函数是最后执行的。

假设我们在 private Integer age = 18; 后面加上

private static Person person = new Person(); 
这段代码后又会输出什么呢?

 其实这也完全符合我们以上的分析,首先初始化静态资源,当到private static Person person = new Person();这一步的时候,实例化了一个Person对象,此时就会调用构造函数,再调用构造之前先加载普通成员变量或普通代码块(第一次执行普通代码块)【这里也会有个疑问?这也是第一次new Person(),怎么不初始化类的静态资源了呢?其实这也很好理解,因为当前的操作就是初始化静态资源,自然的也不可能再一次执行初始化静态资源的操作了吧】,再然后返回调用构造函数,构造函数调用完后继续初始化下面的静态资源,直到类的静态资源全部加载完毕,然后我们测试方法的new Person()开始真正的执行(此时到这里静态资源已经加载过了,所以直接跳过了加载静态资源的步骤),随后调用构造函数之前先加载普通成员变量或普通代码块(第二次执行普通代码块),最后执行构造函数返回实例对象。

假设我们再构造函数中写new Person(),如下:

public Person(){
    new Person();
    value1++;
    value2++;
}

按照上面的理解那么最后就会无限的输出普通代码块的内容直到堆栈溢出,这就相当于是一个死循环了,事实上也的确如此:

 

以上仅仅分析了没有继承关系的单个类的初始化过程,只要我们把单个类的加载顺序理解清楚了,实际上有继承关系的原理是一样的,无非就是先初始化父类然后才到子类,静态资源是全面优先于普通资源的。首先由父到子类的静态资源全部加载完成后才到普通资源,然后就是加载由父到子类的普通资源,当然如果调用了构造函数,那么构造函数都会在最后执行,也就是说返回一个对象之前保证了他的父类首先初始化完成然后自己本身的初始化也完成,这样才是一个完整的实例对象。本质上就是先有父再有子,由父到子全部初始化完成后才能使用这个子类对象,这也完全符合我们日常生活中的自然逻辑。

最后如果你还是对于加载顺序理解不够透彻,那么你可以使用断点调试功能,debug运行一下代码,然后一步一步的调试,这样会更清晰的看到整个加载过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值