在java中正常的实例化逻辑为:
- 父类的静态变量赋值
- 自身的静态变量赋值
- 父类成员变量赋值和父类块赋值
- 父类构造函数赋值
- 自身成员变量赋值和自身块赋值
- 自身构造函数赋值
具体的使用可以参考这篇博客:Java提高篇——静态代码块、构造代码块、构造函数以及Java类初始化顺序。
但是会有特殊情况比如下面这个class的执行情况:
public class StaticTest {
static StaticTest st = new StaticTest();
static {
System.out.println("1");
}
{
System.out.println("2");
}
StaticTest() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b + ",c=" + c);
}
int a = 110;
static int b = 111;
static final int c = 112;
public static void staticFunction() {
System.out.println("4");
}
public static void main(String[] args) {
staticFunction();
}
}
这个例子的输出结果为:
2
3
a=110,b=0,c=112
1
4
为什么会出现这种结果哪,可以看下这篇博客:哥们,你真以为你会做这道JVM面试题?,在这篇博客里面作者分析了为什么会出现这种结果,这里自己再啰嗦几句,将自己的理解用于梳理一下为何会出现这种结果。
首先引一张图就是类的生命周期:
具体的各个生命周期处理的细节可以从详解java类的生命周期这篇博客中学习到,在这里主要分析的是连接 与 初始化这两个阶段。
连接:分为验证,准备,解析三个部分,其中准备 完成为静态变量分配内存空间,所以在程序中为b,c两个个变量分配内存空间,并设置初始值,b为基本类型,所以默认值设为0,c也为基本类型,但是因为其声明为final对象(编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值),所以c设置为其指定值112。
初始化:该阶段开始执行类的构造,按照正常的类的初始化流程(文章头部提到),其整个流程执行过程如下:
首先按照从上向下的方式初始化静态变量,首先遇到的是st这个静态变量,new StaticTest(),所以开始执行类的构造,按照顺序首先从上到下先执行构造代码块,再执行成员变量的赋值,再执行构造函数,所以步骤2,3,4,5依次进行。当执行完st这个静态变量的初始化赋值后,接着向下执行其它静态成员变量的赋值所以步骤6执行。至此,所有静态成员变量初始化完毕,开始执行静态代码块的执行,所以步骤7执行。到了这步所有的静态变量,成员变量都已赋值,类也已经初始化构造完毕,开始执行成员方法,首先执行的是入口函数main,所以第8步执行,8调用静态方法,所以9执行------执行结束。
这就导致出现有趣的现象:“实例初始化竟然出现在静态初始化之前”。这里还需注意的就是a,b,c的初始化值问题。