java中多级继承中构造函数调用的顺序

时间:2013-07-23 09:27:09

标签: java inheritance constructor

//: c07:Sandwich.java
// Order of constructor calls.
// package c07;
// import com.bruceeckel.simpletest.*;

import java.util.*;

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

public class Sandwich extends PortableLunch {
//  private static Test monitor = new Test();
  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();
   /*
   monitor.expect(new String[] {
      "Meal()",
      "Lunch()",
      "PortableLunch()",
      "Bread()",
      "Cheese()",
      "Lettuce()",
      "Sandwich()"
    });
    // */
  }
} ///:~

此代码的输出是

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

由于类中的字段是按声明的顺序创建的,为什么不

Bread()
Cheese()
Lettuce()

来到上面列表的顶部?

此外,它在这段代码中尝试做什么?

   monitor.expect(new String[] {
      "Meal()",
      "Lunch()",
      "PortableLunch()",
      "Bread()",
      "Cheese()",
      "Lettuce()",
      "Sandwich()"
    });  

起初我以为这是一个匿名类,但它看起来不像。它是在初始化一个String数组吗?为什么它没有String变量的名称?请告诉我这里使用的编程结构的名称。

4 个答案:

答案 0 :(得分:23)

构造函数:

public Sandwich() {
    System.out.println("Sandwich()");
}

由编译器翻译为:

public Sandwich() {
    super();   // Compiler adds it if it is not explicitly added by programmer
    // All the instance variable initialization is moved here by the compiler.
    b = new Bread();
    c = new Cheese();
    l = new Lettuce();

    System.out.println("Sandwich()");
}

因此,构造函数中的第一个语句是超类构造函数的链接。实际上,对于那个问题,任何构造函数链中的第一个语句都是超类构造函数。这就是为什么首先调用超类构造函数PortableLunch,由于编译器添加了 super() ,它再次将调用链接到它的超类构造函数(记得吗?)

这个构造函数调用的链接一直到继承层次结构顶部的类,从而在最后调用Object类构造函数。

现在,在每个超类构造函数执行完毕并且所有超类字段都已初始化之后,直接子类构造函数在super()调用之后开始执行。最后它回到Sandwitch()构造函数,现在初始化您的3字段。

因此,基本上您的字段最后会被初始化,因此它们会在最后一次打印,就在打印Sandwitch()之前。

有关实例创建过程的详细说明,请参阅 JLS - §12.5 - Creation of New Class Instance


关于问题的第2部分:

monitor.expect(new String[] {
      "Meal()",
      "Lunch()",
      "PortableLunch()",
      "Bread()",
      "Cheese()",
      "Lettuce()",
      "Sandwich()"
    });  

此代码正在创建未命名数组,并同时初始化一些字符串文字。它类似于创建命名数组的方式:

String[] arr = new String[] { "rohit", "jain" };

答案 1 :(得分:5)

示例中的对象使用继承,这会导致调用一系列构造函数。使用继承时,从另一个(subtype)继承的类必须调用它扩展的类的构造函数(super type)。当存在类型层次结构时,意味着几个类在链中相互扩展,对超级构造函数的调用将传播到链中的第一个类,该类不从另一个类继承(忽略Object)。

必须在执行子类型构造函数之前调用子类的超类构造函数,因为它可能依赖于超类型中的字段或方法。构造函数调用链接类型层次结构,并且每个构造函数初始化后,子类型开始实例化。

一旦调用了类类型层次结构中的超类型构造函数,就会声明子类型的字段,因为子类型的构造函数也可能需要它们。声明子类型的字段后,执行子类型构造函数。

这个顺序是必要的,因为子类型可能依赖于超类型中建立的字段或方法。

Meal() (Top of Class Hierarchy, not including Object)
Lunch() (Extends Meal)
PortableLunch() (Extends Lunch)
Bread() (Field of lunch declared before constructor call)
Cheese() (Field of lunch declared before constructor call)
Lettuce() (Field of lunch declared before constructor call)
Sandwich() (Extends Portable Lunch)

Here is a really good overview of object creation in Java.

答案 2 :(得分:1)

new String[] {
      "Meal()",
      "Lunch()",
      "PortableLunch()",
      "Bread()",
      "Cheese()",
      "Lettuce()",
      "Sandwich()"
    }

\以上是anonymous array declaration。在这种情况下,您无需指定大小。

在初始化实例变量之前,将首先调用所有超类构造函数。 即,

订单--->

  Object(), 
  all your superclass constructors,
  instance variables of this class in that order

答案 3 :(得分:1)

首先调用构造函数,然后根据Java Language Specification中指定的顺序评估其实例变量。这就是你得到的原因

Bread()
Cheese()
Lettuce()

Meal()
Lunch()
PortableLunch()

关于String []初始化,因为您认为不是匿名类,只是一个String对象创建,它不一定必须分配给变量。