从虚拟机角度理解,为什么静态块函数先于构造函数执行

作者:DeppWang原文地址

一、前言

常常有关于静态块函数、构造函数执行顺序的面试题,如果死记硬背,往往容易混淆。需要从虚拟角度来理解,当真正理解后,其实很简单。

一个面试题栗子,请输出下面代码的运行结果:

class StaticSuper {
    static {
        System.out.println("super static block");
    }
StaticSuper() {
    System.out.println("super constructor");
}

}

public class StaticTest extends StaticSuper {
static {
System.out.println("static block");
}

StaticTest() {
    System.out.println("constructor");
}

public static void main(String[] args) {
    System.out.println("in main");
    StaticTest s = new StaticTest();
}

}

执行结果如下:

super static block
static block
in main
super constructor
constructor

二、分析

当执行 StaticTest.main() 时,类加载器加载 StaticTest.class 文件到虚拟机,新建一个与之对应的 Class 对象,如果有类变量,为类变量设置初始值。

执行 StaticTest.main(),其实是执行 invokestatic 指令,Java 虚拟机规范规定,执行 invokestatic 指令时,需要先初始化类,初始化类时,执行类构造器 <clinit>() 方法, <clinit>() 方法为类变量赋值以及执行静态代码块,虚拟机保证执行 <clinit>() 方法前先执行父类 <clinit>() 方法。

执行完 <clinit>() 方法后执行 main() 方法

执行 new 指令时,实例化生成对象,并为实例变量设置初始值(如果没有初始值),再调用实例构造方法 <init>() 为实例变量赋值。

三、加入构造代码块

有时候,为了加大难度,里面还会加上构造代码块

class StaticSuper {
    static {
        System.out.println("super static block");
    }
{
    System.out.println(&quot;super constructor block&quot;);
}

StaticSuper() {
    System.out.println(&quot;super constructor&quot;);
}

}

public class StaticTest extends StaticSuper {
static {
System.out.println("static block");
}

{
    System.out.println(&quot;constructor block&quot;);
}

StaticTest() {
    System.out.println(&quot;constructor&quot;);
}

public static void main(String[] args) {
    System.out.println(&quot;in main&quot;);
    StaticTest s = new StaticTest();
}

}

构造代码块可以看成一个公共构造函数,使用任何构造函数前都需要先执行构造代码块。所以执行结果为:

super static block
static block
in main
super constructor block
super constructor
constructor block
constructor

四、应用

静态代码块属于类构造函数的范畴,所以常用于设置静态变量。如,Integer 里面的 IntegerCache。

public final class Integer extends Number implements Comparable<Integer> {
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
    static {
        // high value may be configured by property
        int h = 127;
        ...
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k &lt; cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high &gt;= 127;
    }
    ...
}

五、总结

1、我们将静态代码块看成类构造方法,类构造方法肯定先于实例构造方法执行。

2、构造代码块可以看成公共构造函数,先于构造函数执行

这方面的内容可以《深入理解 Java 虚拟机》(第 3 版)- 7.3 类加载的过程,会比看博文理解得更深刻。

posted @ 2020-05-07 14:00  DeppWXQ  阅读(251)  评论(0编辑  收藏  举报