在使用继承时,静态块和初始化块的执行顺序是什么?

99

我有两个类,一个是父类(Parent),一个是子类(Child)

public class Parent {    
    public Parent() {
        System.out.println("Parent Constructor");
    }    
    static {
        System.out.println("Parent static block");    
    }    
    {
        System.out.println("Parent initialisation  block");
    }
}

public class Child extends Parent {    
    {
        System.out.println("Child initialisation block");
    }
    static {
        System.out.println("Child static block");
    }

    public Child() {
        System.out.println("Child Constructor");
    }    
    public static void main(String[] args) {
        new Child();    
    }
}

上面代码的输出结果将会是:

Parent static block
Child static block
Parent initialization  block
Parent Constructor
Child initialization block
Child Constructor

为什么Java按照那个顺序执行代码?决定执行顺序的规则是什么?


随着Java的更新版本(1.7及以上),即使您有一个静态块,JVM也会抛出运行时异常,因为它在类中找不到主方法。 - lft93ryt
11个回答

77

我学习时需要有视觉辅助,因此这里提供了一个可视化的示例代码,作为SSCCE

public class Example {

    static {
        step(1);
    }

    public static int step_2 = step(2);
    public int step_8 = step(8);

    public Example(int unused) {
        super();
        step(10);
    }

    {
        step(9);
    }

    // Just for demonstration purposes:
    public static int step(int step) {
        System.out.println("Step " + step);
        return step;
    }
}
public class ExampleSubclass extends Example {

    {
        step(11);
    }

    public static int step_3 = step(3);
    public int step_12 = step(12);

    static {
        step(4);
    }

    public ExampleSubclass(int unused) {
        super(step(7));
        step(13);
    }

    public static void main(String[] args) {
        step(5);
        new ExampleSubclass(step(6));
        step(14);
    }
}

这将打印:

Step 1
Step 2
Step 3
Step 4
Step 5
Step 6
Step 7
Step 8
Step 9
Step 10
Step 11
Step 12
Step 13
Step 14

记住,static 部分的顺序很重要;回顾一下ExampleExampleSubclass之间static内容顺序的区别。

同时注意,在构造函数中的 super() 调用之后立即执行实例初始化块(即使该调用是隐含/省略 的),这个顺序始终如一。然而,初始化块和字段初始化器之间的顺序确实很重要。


5
请附上任何负评的原因,这样我就知道如何在未来写更好的答案了 :) - Ky -
1
“实例初始化块总是在构造函数之前执行”:不,它是在构造函数期间执行的,在super()调用之后。你的输出并没有证明这一点。它在super()之后的构造函数中的任何代码行之前执行。这不是同一件事情。 - user207421
谢谢您告诉我,@user207421。我很难想象您试图表达什么;当我在构造函数中在super()之前放置另一个step()调用时,我会得到这些错误:https://photos.app.goo.gl/9ToHkTVuAutpjrbm7 - 所以我不知道如何测试是否发生了某些事情在super()调用之前。 - Ky -
啊哈,我想我明白了!我会更新我的问题。谢谢,@user207421! - Ky -

61

这里有几个规则:

  • 静态块总是在对象创建之前运行,所以您会看到来自父类和子类静态块的打印消息。
  • 现在,当您调用子类(child)的构造函数时,该构造函数在执行自己的构造函数之前会隐式调用super();。初始化块甚至在构造函数调用之前就开始起作用,所以它会首先被调用。因此,现在您的父类已经创建,程序可以继续创建子类,子类将经历相同的过程。

解释:

  1. 父类的静态块首先执行,因为它是首先加载的,并且静态块是在类加载时调用的。

因为派生类的基类是由您创建的父类。 - Ankush Bist
实例初始化块在构造函数期间发挥作用,在执行super()之后。 - user207421

9

首先 - 运行子类 (注释掉extend语句) 以查看简单流程。

其次 - 前往Java中的静态块和初始化块有什么区别?并阅读那里被接受的答案。

编辑:

  1. 执行遵循SIC的顺序 - 静态块,(非静态)初始化块和构造函数。
  2. (非静态)初始化块复制到每个构造函数的顶部! (因此出现了第3/4/5/6行)
  3. 在类初始化之前,必须初始化其直接超类 - http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4 (因此父类静态块首先出现)。

9
  • 静态初始化块在类加载时执行。
  • 在类层次结构中,静态初始化块的执行顺序从顶级类开始。
  • 在一个类中,静态块的执行顺序是从上到下的。
  • 无论静态块位于类的哪个位置,上述规则都适用。

(在您的代码中,父类的静态块将首先执行,然后是子类的静态块。)

  • 实例初始化块将在调用超类构造函数(super();)之后执行。
  • 默认构造函数中总是super();语句最先执行。

在您的代码中创建Child对象时:

  • Child类的默认构造函数会被执行。
  • 它会调用super();构造函数。
  • 然后执行超类构造函数。
  • Parent类将执行其超类构造函数。
  • 之后,将执行Parent类中的实例初始化块(从上到下)。
  • 然后执行构造函数中的代码(如果有)。
  • 接着返回到Child类并执行Child类的实例初始化块。
  • 最后执行子类构造函数中的代码(如果存在)。

你的第二点是正确的,而第三点与其相矛盾,因此是不正确的。 - user207421
好处是:实例初始化块将在构造函数中调用super()之后执行。 - Ray Jasson

5
在Java中,静态块会在main方法之前执行。如果我们在Java类中声明静态块,则该块在类加载时执行。这是使用静态变量进行初始化的常用方法。每次类加载时都会执行Java中的静态块,也被称为静态初始化块。当类加载到内存中时,静态块会初始化,这意味着JVM读取字节码时会进行初始化。初始化可以是变量初始化或任何应该由该类的所有对象共享的其他内容。静态块是一个普通的代码块,括在大括号{}中,并以static关键字开头。
因此,静态块首先被执行。
实例初始化块:每次创建类的实例时运行。
因此,在创建类的实例时,接下来执行初始化块。
然后执行构造函数。

为什么父类的静态块会先执行...?我是先加载子类的啊? - CKR666
@CKR666 加载子类需要先加载其父类。不初始化父类就毫无意义。 - user207421

4

我想分享一下我的发现。在另一篇帖子的其中一个回答中,有人说静态块先于静态字段执行,这是不正确的。它取决于静态字段和静态块哪个先出现。看一下下面的代码,可以更好地理解。

  1. JVM looks for a class which has public static void main(String args[]) so that it can load that class.
  2. It then initialises static fields of this class(if they come before static blocks). These fields can call static methods of this class or another. If they call static method of this class then that method gets served. If they call static method of another class, then static fields or blocks of that class(depending on which comes first) gets initialised first, then this method call is served.
  3. Then, it moves to static blocks.
  4. It comes back to main method.

    class TestLab {
    static int method(String a) {
        System.out.println("in static method of TestLab" + " Coming from " + a);
        System.out.println("b is " + b);
        return 6;
    }
    
    static int a = method("Line 11");
    static int b = 7;
    
    TestLab() {
        System.out.println("Inside test lab constructor");
    }
    
    static {
        System.out.println("In static block of TestLab");
    }
    
    }
    
    public class Test1 {
    public static void main(String[] args) {
        System.out.println("inside main method of Test 1");
        int a = TestLab.method("Line 26");
    }
    
    // static Test ref=new Test();
    Test1() {
        System.out.println("Default Constructor of Test1");
    }
    
    {
        System.out.println("In instance block of Test1");
    }
    static int d = TestLab.method("Line 37");
    static int e = methodOfTest1();
    static {
        System.out.println("In Static Block of Test1");
    }
    
    static int methodOfTest1() {
        System.out.println("inside static method:mehtodOfTest1()");
        return 3;
    }
    }
    

以下是输出结果:

in static method of TestLab Coming from Line 11
b is 0
In static block of TestLab
in static method of TestLab Coming from Line 37
b is 7
inside static method:mehtodOfTest1()
In Static Block of Test1
inside main method of Test 1
in static method of TestLab Coming from Line 26
b is 7

你自相矛盾了。静态字段和初始化块按照它们在源文件中出现的顺序执行,而不管它们是哪一个。 - user207421
@user207421,抱歉,我没听懂你的意思。我写了下面这段话,这就是输出所说明的内容:_它取决于静态字段或静态块哪个先出现_。 - pragun

3

使用逐步调试器检查对象构建过程,可以非常有帮助,您可以在其中查看对象如何经过各个阶段。我发现这对从更高的角度清晰地看问题非常有用。Eclipse可以通过其调试器“step into”功能来帮助您实现此目标。


2

在准备认证时,我发现了以下内容。

在运行类时,首先进行静态块/静态变量初始化。如果有多个静态块,则按它们出现的顺序执行。

然后会执行init块/实例变量初始化。如果有多个init块/变量初始化,则按它们出现的顺序执行。

之后会查看构造函数。


1

当一个类被加载到JVM时,静态块会被执行。而初始化块会被复制到构造函数中,该构造函数将创建对象,在对象创建之前运行。


1

控制流程是-

静态块 -> 初始化块 -> 最后是构造函数。

静态块 -> 当控制流程到达该类时(JVM加载该类),此静态块将仅执行一次

初始化块 -> 每当为该类创建新对象时,此初始化块将被执行(它将从构造函数的第二个语句开始执行,然后执行以下构造函数语句-请记住构造函数的第一个语句将是Super()/this())

构造函数 -> 每当创建新对象时,就会执行此操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接