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

zaq34kh6  于 2023-03-28  发布在  Java
关注(0)|答案(5)|浏览(134)

请看下面的代码:

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

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

  class Cheese {
    Cheese() { System.out.println("Cheese()"); }
  }
public static void sdadsdt(){

}
  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()之前执行,尽管它们的构造函数是在之后调用的。

rqmkfv5c

rqmkfv5c1#

这些是示例字段

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
bxfogqkk

bxfogqkk2#

我认为这里有两件事让你很困惑。第一是main是一个静态方法,其中作为成员变量的b,c和l是非静态示例变量。这意味着它们属于类的对象,而不是类本身。所以当类被初始化以运行main方法时,Bread,Cheese和Lettuce的构造器不会被调用,因为还没有创建Sandwich的示例。
直到main实际运行,调用new Sandwich()才是实际构造的任何对象。此时,操作顺序为:
1.初始化基类的成员字段
1.运行基类构造函数
1.初始化该类成员字段
1.运行该类的构造函数
这是递归完成的,因此在本例中,顺序为

  1. Meal的初始化字段(无)
    1.运行Meal的构造函数(打印“Meal”)
  2. Lunch的init字段(无)
    1.运行Lunch的构造函数(打印“Lunch”)
  3. PortableLunch的init字段(无)
    1.运行PortableLunch的构造函数(打印“PortableLunch”)
  4. Sandwich的init字段(打印“Bread”、“Cheese”和“Lettuce”)
    1.运行Sandwich的构造函数(打印“Sandwich”)
    这个顺序的目的是为了确保在子类中的任何代码运行之前,基类被完全初始化。这是必要的,因为在子类的构造函数中,它可能会调用基类上的方法。如果基类没有首先初始化它的成员,就会发生糟糕的事情。
oyt4ldly

oyt4ldly3#

即使它们的构造函数是在后面调用的。
不是之后,这里的constructor方法看起来像编译器:

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()");
}
dwbf0jvd

dwbf0jvd4#

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

ovfsdjhp

ovfsdjhp5#

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

这些初始化器在超级调用其父类之后由编译器放入Sandwich构造函数中。
如果它们是静态的,那么它们将首先发生,因为静态初始化器发生在类装入时。

相关问题