Fork me on GitHub

java中类的普通初始化块一定在静态初始化块后运行吗

大部分教程都会告诉我们静态初始化块和静态字段总是在初始化块和普通类字段前运行,事实上也确实如此,直到我看到下面这样的代码:

public class Test {

	static Test test = new Test();

	{
		System.out.println("normal");
	}

	static{
		System.out.println("static");
	}

	public static void main(String [] args){
		Test test = new Test();
	}

}

可以先猜一猜结果,然后我们编译运行:

$ javac Test.java
$ java Test

normal
static
normal

难道不应该是先打印出static才对吗?这里我就不卖关子了,答案很简单。我们先来看一段话:

every constructor written in the Java programming language (JLS §8.8) appears as an instance initialization method that has the special name <init>. The initialization method of a class or interface has the special name <clinit>.

这段话来自jls,意思是普通的构造函数和初始化块会被放在一个叫<init>的方法里,在创建对象实例时调用;而类自身的初始化包括静态字段和静态方法会被放在叫<clinit>的方法里。而静态字段和静态块的初始化优先级相同,顺序遵从在代码中排列的顺序,也就是谁在前面谁先执行。

因此执行顺序已经明了了:

  1. 因为main函数中要创建类的实例,所以类会先被加载,这是调用了<clinit>
  2. 静态字段在静态初始化块之前,所以先执行初始化
  3. 静态字段调用了new Test()创建类的实例,这时调用了<init>
  4. 因为普通的初始化块在<init>里,所以被调用,首先打印出了normal
  5. 静态字段初始化完成后开始执行紧随其后的静态块,打印出static
  6. 随后回到main函数中,类的初始化只会运行一次,所以这次只有<init>运行,打印出normal

因此看起来像是普通初始化块在静态块之前运行了,实际上只是静态字段的初始化导致了普通初始化块的提前执行。

知道了原理后,想下面这样的代码会输出什么自然也不在话下了:

public class Init {
    private int v = 0;
    static Init obj2 = new Init(); // 只能是static字段,想一想为什么

    static {
        System.out.println("static 2");
    }

    static Init obj1 = new Init();

    {
        System.out.println("from init");
    }

    static {
        System.out.println("static 1");
    }

    public static void main(String[] args) {
        var o1 = new Init();
        var o2 = new Init();
    }
}

当然,在实际的编码中静态块应该只负责静态字段的处理,普通的初始化块只负责对普通类字段的处理,上面的代码是不应该被模仿的。

参考

https://www.zhihu.com/question/50374553

posted @ 2020-09-10 15:28  apocelipes  阅读(321)  评论(0编辑  收藏  举报