22

考虑下面的代码

  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()

我的困惑是乳清 Meal() Lunch() 和 PortableLunch() 在 Bread() Cheese() 和 Lettuce() 之前运行,即使它们的构造函数在之后调用。

4

5 回答 5

26

这些是实例字段

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
于 2013-10-16T15:18:04.300 回答
7

我认为这里发生了两件事让你失望。第一个是main静态方法,其中成员变量 b、c 和 l 是非静态实例变量。这意味着它们属于类的对象,而不是类本身。因此,当初始化类以运行 main 方法时,不会调用 Bread、Cheese 和 Lettuce 的构造函数,因为还没有创建 Sandwich 的实例。

直到 main 实际运行,并且调用new Sandwich()是实际构造的任何对象。此时,操作顺序为:

  1. 初始化基类的成员字段
  2. 运行基类构造函数
  3. 初始化该类的成员字段
  4. 运行这个类的构造函数

这是递归完成的,所以在这种情况下,顺序是

  1. Meal 的初始化字段(无)
  2. 运行 Meal 的构造函数(打印“Meal”)
  3. 午餐的初始化字段(无)
  4. 运行午餐的构造函数(打印“午餐”)
  5. PortableLunch 的初始化字段(无)
  6. 运行 PortableLunch 的构造函数(打印“PortableLunch”)
  7. Sandwich 的 init 字段(打印“Bread”、“Cheese”和“Lettuce”)
  8. 运行 Sandwich 的构造函数(打印“Sandwich”)

此命令的目的是确保在运行子类中的任何代码之前完全初始化基类。这是必需的,因为在子类的构造函数中,它可能会调用基类上的方法。如果该基类没有首先初始化其成员,就会发生不好的事情。

于 2013-10-16T15:30:46.350 回答
6

即使他们的构造函数在之后调用。

不是之后,这里的 contrustor 方法看起来像编译器:

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()");
}
于 2013-10-16T15:21:19.607 回答
4

构造函数中的第一个调用始终是super(...). 如果你没有明确地写下来,编译器会自动插入这个调用。在调用返回之前不能调用构造的对象super()。完成super()后,按照出现的顺序初始化字段,然后执行其余的构造函数。

于 2013-10-16T15:26:57.490 回答
0
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();

在超级调用它的父类之后,编译器将这些初始化器放入 Sandwich 构造函数中。

如果这些是静态的,那么它们会首先发生,因为静态初始化器发生在类加载时。

于 2013-10-16T15:18:35.533 回答