Java继承中构造函数的执行顺序:为什么会出现两次Shape输出?

Java继承中构造器执行顺序详解:揭秘两次“shape”输出之谜

本文分析一段Java继承代码,解释其构造器执行顺序,并解答为何出现两次“shape”输出的疑惑。

以下代码片段来自《on java》基础卷,展现了Java继承中构造器调用的非直观特性:

class Shape {
    Shape() {
        System.out.println("Shape");
    }
}

class Line extends Shape {
    Line() {
        super();
        System.out.println("Line");
    }
}

public class CADSystem extends Shape {
    private Line li = new Line(); // 关键点1
    public CADSystem() {
        super(); // 关键点2
        System.out.println("CADSystem");
    }

    public static void main(String[] args) {
        CADSystem x = new CADSystem();
    }
}

运行结果为:

Shape
Shape
Line
CADSystem

与预期结果(Shape, Line, Shape, CADSystem)不符。 这并非简单的变量初始化优先于方法调用,而是Java构造器执行顺序的体现。

关键在于理解Java构造器初始化的步骤:

  1. 成员变量初始化:CADSystem类的构造器执行之前,其成员变量li会被初始化。 li的初始化需要调用Line的构造器,而Line的构造器又会调用Shape的构造器,因此第一次输出"Shape"。

  2. 父类构造器调用: CADSystem的构造器显式调用了super() (关键点2),这会调用Shape的构造器,导致第二次输出"Shape"。

  3. 构造器主体执行: 接着,Line构造器的剩余部分执行,输出"Line"。最后,CADSystem构造器的剩余部分执行,输出"CADSystem"。

因此,输出顺序为"Shape" (来自li的初始化), "Shape" (来自super()), "Line", "CADSystem"。 这并非变量初始化在方法调用之前那么简单,而是Java语言规范中对构造器初始化顺序的严格规定导致的。 父类构造器的调用发生在子类构造器主体执行之前,而成员变量的初始化又发生在父类构造器调用之前。

总而言之,理解Java中构造器执行顺序的关键在于:成员变量初始化 -> 父类构造器调用 -> 子类构造器主体执行。 这三个步骤的顺序是固定的,并且成员变量的初始化会触发一

系列的构造器调用,这在继承关系中尤为重要。