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

作者: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("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 类加载的过程,会比看博文理解得更深刻。

评论默认使用 ,你也可以切换到 来留言。