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

时间:2013-10-16 15:14:42

标签: 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() { 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()之前运行,即使它们的构造函数在之后被调用。

5 个答案:

答案 0 :(得分:24)

这些是实例字段

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

答案 1 :(得分:6)

  

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

不是之后,这里的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()");
}

答案 2 :(得分:6)

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

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

  
      
  1. 初始化基类的成员字段
  2.   
  3. 运行基类(s)构造函数
  4.   
  5. 初始化此类的成员字段
  6.   
  7. 运行此类的构造函数
  8.   

这是递归完成的,因此在这种情况下,订单将是

  
      
  1. 膳食的初始字段(无)
  2.   
  3. 运行Meal的构造函数(打印“Meal”)
  4.   
  5. 午餐的初始字段(无)
  6.   
  7. 运行午餐的构造函数(打印“午餐”)
  8.   
  9. PortableLunch的init字段(无)
  10.   
  11. 运行PortableLunch的构造函数(打印“PortableLunch”)
  12.   
  13. 三明治的初始字段(打印“面包”,“奶酪”和“生菜”)
  14.   
  15. 运行三明治的构造函数(打印“三明治”)
  16.   

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

答案 3 :(得分:4)

构造函数中的第一个调用始终是super(...)。如果您没有明确地将其写下,则编译器会自动插入此调用。在调用super()之前,不会对构造对象进行调用。 super()完成后,将按照外观顺序初始化字段,然后执行构造函数的其余部分。

答案 4 :(得分:0)

private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();

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

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