Java存在先执行构造方法后执行静态代码块的情况及原因
自己在第一遇到这个问题是在牛课网做Java的选择题的时候,那个时候感觉到很疑惑。然后自己照着上面的内容码了一遍,代码如下:
public class ExtendsTest {
public static void main(String[] args) {
new B();
}
}
class A{
//这个时候由于在初始化类B的时候类A还没进行初始化,因此要进行A的初始化即执行<cinit>
//而在这里的时候由于类A的初始化已经被触发了,所以这里就不需要在进行类A的初始化即不需要再执行<cinit>,而是直接执行实例构造方法<init>
private static A a = new A();
static{
b = new A();
//System.out.println(b);
System.out.println("A静态块执行");
}
public A(){
System.out.println("A的构造方法执行了");
}
private static A b;
}
class B extends A{
private static B c = new B();
static {
System.out.println("B静态块执行");
}
public B(){
System.out.println("B的构造方法执行了");
}
}
运行结果如下:
从上面的运行结果可以看出,先执行了A的构造方法后面才执行A的静态代码块,原因在哪呢?
下面为个人的理解:
在解答这个问题前我们需要知道一个类是什么时候进行初始化的,或者说是什么情况下会进行初始化(注意这里写的是类的初始化(感兴趣的同学可以去了解一下类的加载机制)),在《深入理解Java虚拟机》一书中提到:对于类的初始化阶段,虚拟机规范是严格规定了有且只有以下五种情况必须立即对类进行初始化(我们这里关注的是第三点):
- 使用new关键字实例化对象的时候、读取或设置一个静态变量的时候(被final修饰、已在编译器把结果放入常量池的静态字段除外)、以及调用一个类的静态方法的时候
- 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行初始化,则先触发器初始化。
- 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动的时候,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类。
- 当使用JDK7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
到这我们开始进行分析:
从上面的代码可以看出,main方法中new的是一个子类对象,这个时候有第一点可得其会对子列进行初始化,但这个时候看第三点,我们可以知道这个时候需要对父类进行初始化,这里就可以解释父类先于子类之前初始化。
下面我们在说说为什么中为什么会先执行构造方法:
对于父类其中包含两个静态成员和一个静态代码块,在说这个之前我们需要了解类的加载过程:加载->验证->准备->解析->初始化(想详细了解其具体信息的可以再自行百度)。对于加载过程中的静态变量,他们会在准备阶段进行一次初始化,值得注意的是这里的初始化赋的值为其类型对应的零值,而不是源代码中赋的初始值。其真正进行程序中的初始值实在加载的最后一个阶段进行的(即初始化过程),在这个过程如果代码中包含静态变量或者代码块,jvm会利用一个< cinit > 函数对这些静态的东西按照顺序进行初始化(这里的初始化赋值为程序代码中的值)。因此上面的代码中,父类中的第一个静态属性赋值为new A(),在这个类初始化的过程中进行赋值,所以其打印的是构造方法里面的内容,虽然这里使用到了new关键字但由于类A已经进行了加载并在进行初始化因此并不需要再次加载所以这里的new A()通过调用 < init >方法来构造类A的对象。对于与不了解的可以自行查找相关的解释。这里只简单的说以下,< init >相当于构造方法,在每次构造一个对象的时候会执行一次,而< cinit >是为了初始化类的静态成员和代码块的。因此其只会执行一次。
总结
上面的内容完全是个人的理解,希望对你有帮助,如果有存在不正确的地方还望指出。