考虑以下代码
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()之前运行,即使它们的构造函数在之后被调用。
答案 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()
是实际构造的任何对象。此时,操作顺序为:
- 初始化基类的成员字段
- 运行基类(s)构造函数
- 初始化此类的成员字段
- 运行此类的构造函数
醇>
这是递归完成的,因此在这种情况下,订单将是
- 膳食的初始字段(无)
- 运行Meal的构造函数(打印“Meal”)
- 午餐的初始字段(无)
- 运行午餐的构造函数(打印“午餐”)
- PortableLunch的init字段(无)
- 运行PortableLunch的构造函数(打印“PortableLunch”)
- 三明治的初始字段(打印“面包”,“奶酪”和“生菜”)
- 运行三明治的构造函数(打印“三明治”)
醇>
此顺序的目的是确保在运行子类中的任何代码之前完全初始化基类。这是必需的,因为在子类的构造函数内,它可以调用基类的方法。如果该基类没有首先初始化其成员,那么就会发生不好的事情。
答案 3 :(得分:4)
构造函数中的第一个调用始终是super(...)
。如果您没有明确地将其写下,则编译器会自动插入此调用。在调用super()
之前,不会对构造对象进行调用。 super()
完成后,将按照外观顺序初始化字段,然后执行构造函数的其余部分。
答案 4 :(得分:0)
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
在超级调用它的父类之后,编译器将这些初始化程序放入Sandwich构造函数中。
如果这些是静态的,那么它们将首先发生,因为静态初始化器发生在类加载上。