类的初始化包括静态属性的初始化和实例属性的初始化,在加载类过程中的准备阶段会给静态属性分配存储空间,并根据属性的类型设置初始值,例如 int 类型静态属性会设置初始值为 0,布尔类型静态属性会设置初始值为 false;实例属性的初始化会在构造实例对象时发生。

这里提到的静态属性不包括静态常量,静态常量会在准备阶段直接赋予指定的初始值。

构造实例对象

输出结果为:

由此可知,静态属性的初始化发生在实例属性的初始化之前,且只发生一次,实例属性在每次创建实例对象会进行初始化。

在加载阶段之后,会在内存中保存一个类的java.lang.Class对象,通过该Class对象来访问类中的各项数据,包括静态属性。所以静态属性只需要初始化赋值一次,生成Class对象后,通过Class对象来访问静态属性,实例属性则会在每次生成实例对象时初始化一次。

加载、验证、准备阶段的顺序只是开始顺序,在某一个时间点,可能同时执行多个阶段的任务。例如加载阶段会将二进制字节流转换为内存中的一个Class对象,获取二进制信息后,会启动验证阶段对字节流进行安全验证,验证通过,准备阶段进行静态属性的内存分配和默认值设置,最后才会生成一个Class对象。

构造子类实例对象

输出结果为:

由此可知,在进行子类静态属性初始化时,若父类还没有进行静态初始化,则会先进行父类的静态初始化;在子类实例属性初始化时,若父类还没有进行实例属性初始化,则会先进行父类的实例属性初始化。并且每次构造实例对象都会进行实例属性初始化。

静态属性的定义和使用

输出结果为:

上面提到在准备阶段会对静态属性设置类型默认值,由此可知,示例代码中的静态变量 i,在准备阶段默认值为 0。在初始化阶段,静态代码块和静态变量定义相当于两次赋值,首先设置 i=1,然后 i=2,所以输出值为 2。

通过该示例可知,静态代码块和静态变量定义的赋值操作区分前后顺序,这里有一点需要注意:静态代码中只能访问定义在静态代码块之前的变量,即不能访问未初始化的变量。例如如下情况会编译报错: