Java构造函数-继承层次结构中的执行顺序

Java构造函数-继承层次结构中的执行顺序,java,Java,考虑下面的代码 class Meal { Meal() { System.out.println("Meal()"); } } class Bread { Bread() { System.out.println("Bread()"); } } class Cheese { Cheese() { System.out.println("Cheese()"); } } class Lettuce { Lettuce() { Syst

考虑下面的代码

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

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

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

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

  class Lunch extends Meal {
    Lunch() { System.out.println("Lunch()"); }
  }

  class PortableLunch extends Lunch {
    PortableLunch() { System.out.println("PortableLunch()");}
  }

  class Sandwich extends PortableLunch {
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Lettuce l = new Lettuce();
    public Sandwich() {
      System.out.println("Sandwich()");
    }
    public static void main(String[] args) {
      new Sandwich();
    }
  } 
基于我对类成员初始化和构造函数执行顺序的理解。我希望输出是

Bread()
Cheese()
Lettuce()
Meal()
Lunch()
PortableLunch()    
Sandwich()
因为我相信类成员在调用main方法之前就已经初始化了。然而,当我运行程序时,我得到了下面的输出

Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

我的困惑是,在Bread()Cheese()和莴苣()之前运行whey the mean()午餐()和portable午餐(),即使它们的构造函数在后面调用。

这些是实例字段

private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
它们仅在创建实例时存在(执行)

程序中运行的第一件事是

public static void main(String[] args) {
     new Sandwich();
}
超级构造函数作为每个构造函数中的第一件事被隐式调用,即在
System.out.println

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

class Lunch extends Meal {
    Lunch() { System.out.println("Lunch()"); }
}

class PortableLunch extends Lunch {
    PortableLunch() { System.out.println("PortableLunch()");}
}
调用
super()
后,实例字段在构造函数代码之前再次实例化

顺序颠倒了

new Sandwich(); // prints last
// the instance fields
super(); // new PortableLunch() prints third
super(); // new Lunch() prints second
super(); // new Meal(); prints first
这些初始值设定项在超级调用其父类后由编译器放入三明治构造函数中

如果这些是静态的,那么它们将首先发生,因为静态初始值设定项发生在类加载时

即使他们的构造器在哪里被调用

不是之后,这里是construstor方法对编译器的外观:

public Sandwich(){
    super();// note this calls super constructor, which will call it's super and so on till Object's constructor
    //initiate member variables
    System.out.println("Sandwich()");
}

构造函数中的第一个调用总是
super(…)
。如果没有显式地写下,编译器会自动插入此调用。在返回对
super()
的调用之前,不能对构造的对象进行任何调用。
super()
完成后,字段按出现顺序初始化,然后执行构造函数的其余部分

我觉得有两件事让你很反感。第一个是
main
是一个静态方法,其中成员变量b、c和l是非静态实例变量。这意味着它们属于类的对象,而不是类本身。因此,当初始化该类以运行main方法时,不会调用Bread、Cheese和莴苣的构造函数,因为没有创建三明治的实例

直到main真正运行,调用
newsandwich()
才是实际构造的对象。此时,操作顺序为:

  • 初始化基类的成员字段
  • 运行基类构造函数
  • 初始化此类的成员字段
  • 运行这个类的构造函数
  • 这是递归完成的,因此在本例中,顺序为

  • 餐的初始字段(无)
  • 运行膳食构造器(打印“膳食”)
  • 午餐初始字段(无)
  • 运行午餐构造函数(打印“午餐”)
  • Portable午餐的初始字段(无)
  • 运行Portable午餐的构造函数(打印“Portable午餐”)
  • 三明治的初始字段(打印“面包”、“奶酪”和“生菜”)
  • 运行三明治的构造函数(打印“三明治”)
  • 此顺序的目的是确保在运行子类中的任何代码之前,基类已完全初始化。这是必需的,因为在子类的构造函数中,它可以调用基类上的方法。如果基类没有首先初始化其成员,则会发生不好的事情。

    可能的重复请参见此答案:将使您的概念更清晰。进一步阅读:指定处理构造函数的5个步骤。参数、Alt构造函数(递归)、超级构造函数(递归)、初始值设定项和主体
    public Sandwich(){
        super();// note this calls super constructor, which will call it's super and so on till Object's constructor
        //initiate member variables
        System.out.println("Sandwich()");
    }