从虚拟机角度理解,为什么静态块函数先于构造函数执行
一、前言
常常有关于静态块函数、构造函数执行顺序的面试题,如果死记硬背,往往容易混淆。需要从虚拟角度来理解,当真正理解后,其实很简单。
一个面试题栗子,请输出下面代码的运行结果:
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("super constructor block"); } StaticSuper() { System.out.println("super constructor"); }
}
public class StaticTest extends StaticSuper {
static {
System.out.println("static block");
}{ System.out.println("constructor 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 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 < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } ... }
五、总结
1、我们将静态代码块看成类构造方法,类构造方法肯定先于实例构造方法执行。
2、构造代码块可以看成公共构造函数,先于构造函数执行
这方面的内容可以《深入理解 Java 虚拟机》(第 3 版)- 7.3 类加载的过程,会比看博文理解得更深刻。