Java类的初始化

类的初始化是一个Java类生命周期中的其中一个阶段。如下图所示:


生命周期中的前五个阶段(加载、验证、准备、解析、初始化)是一个类在JVM中的完整加载过程。初始化是类加载的最后一个阶段,也正是在初始化阶段,才会真正开始执行类中所写的Java代码。

Java虚拟机规范中严格规定了有且只有四种情况必须立即对类进行初始化:

1.遇到new、getstatic、putstatic、或者invoicestatic这4条字节码指令时,如果类没有进行过初始化,则需要触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译期就把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

3.当初始化一个类的时候,如果发现父类还没有进行过初始化,需要先触发其父类的初始化。

4.当虚拟机启动时,用户需要指定一个主类(包含main方法的那个类),虚拟机会先初始化这个主类。

这四种场景中的行为称为对一个类进行主动引用除此之外引用类的方式,都不会触发初始化,被称为被动引用

初始化一个类时,最先执行的是静态域中代码而静态域中包含静态变量、静态块和静态方法,其中需要初始化的是静态变量和静态块,这两者的执行顺序由代码的书写顺序决定。静态域中的代码只会被执行一次。如果是使用new关键字实例化对象,静态域中的代码被执行后,紧接着会执行构造代码块和构造函数来实例化对象,这部分代码会执行多次,每次使用new关键字实例化对象时,这部分代码都会执行。可以用如下一段程序验证这些代码的执行顺序:

public class ClassInitTest {
    public static ClassInitTest c1 = new ClassInitTest();
    public static ClassInitTest c2 = new ClassInitTest();
    {
        System.out.println("构造代码块执行");
    }
    static {
        System.out.println("静态代码块执行");
    }
    public ClassInitTest(){
        System.out.println("构造方法被执行");
    }
    public static void main(String[] args){
        System.out.println("main方法的第一行被执行");
        ClassInitTest c = new ClassInitTest();
        System.out.println("main方法的第二行被执行");
    }
}

ClassInitTest是主类,当JVM启动时会对该类进行初始化,首先初始化静态域中的代码,可以看到首先是两个静态变量c1和c2,对c1、c2声明的同时进行变量值的初始化,其值是实例对象,因此构造代码块和构造方法会被执行,new了两次,因此会被执行两次。紧接着会执行静态代码块中的代码。静态域中的代码执行完毕后便开始执行main方法,先是输出一行语句,接着又实例化了一个对象,构造代码块和构造方法会被执行,最后又输出一行语句。因此执行的结果如下:


如果存在继承的情况,初始化子类时,会先初始化父类,同时这几类代码执行顺序的优先级保持不变。实例代码如下:

public class Father {
    static {
        System.out.println("父类的静态代码块被执行");
    }
    {
        System.out.println("父类的构造代码块被执行");
    }
    public Father(){
        System.out.println("父类的构造方法被执行");
    }
}
public class Child extends Father{
    public Child(){
        System.out.println("子类构造方法被执行");
    }
    {
        System.out.println("子类的构造代码块被执行");
    }
    static {
        System.out.println("子类的静态代码块被执行");
    }
    public static void main(String [] args){
        new Child();
    }
}

执行结果如下:


之所以子类的构造方法执行前会先执行父类的构造方法,是因为子类的构造方法总是先调用父类的某个构造方法,也就是说,如果子类没有明显地指明使用父类的哪个构造方法,子类就调用父类不带参数的构造方法。

子类中使用super关键字调用父类的构造方法,而且super必须是子类构造方法的第一条语句,如果子类构造方法中没有明显地写出super关键字调用父类的哪个构造方法,那么默认在子类构造方法中有如下语句(即使不写):

super();//调用父类的无参构造函数

当然也可以在子类的构造方法中显式地用super语句指明调用父类的哪个构造方法(如果父类有多个构造方法的话)

super(实参列表);

参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践/周志明著。



  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值