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

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

4

4 回答 4

25

构造函数:

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 - 创建新类实例


至于你问题的第二部分:

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

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

String[] arr = new String[] { "rohit", "jain" };
于 2013-07-23T09:33:46.837 回答
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)

这是Java中对象创建的一个非常好的概述。

于 2013-07-23T09:30:32.613 回答
1
new String[] {
      "Meal()",
      "Lunch()",
      "PortableLunch()",
      "Bread()",
      "Cheese()",
      "Lettuce()",
      "Sandwich()"
    }

\以上是匿名数组声明。在这种情况下,您不需要指定大小。

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

订购--- >

  Object(), 
  all your superclass constructors,
  instance variables of this class in that order
于 2013-07-23T09:32:12.560 回答
1

首先调用构造函数,然后根据Java 语言规范中指定的顺序评估其实例变量。这就是为什么你得到

Bread()
Cheese()
Lettuce()

Meal()
Lunch()
PortableLunch()

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

于 2013-07-23T09:32:32.150 回答