一、前言
常常有关于静态块函数、构造函数执行顺序的面试题,如果死记硬背,往往容易混淆。需要从虚拟角度来理解,当真正理解后,其实很简单。
一个面试题栗子,请输出下面代码的运行结果:
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 类加载的过程,会比看博文理解得更深刻。