【问题标题】:order of constructor calls in multilevel inheritance in java [duplicate]java中多级继承中的构造函数调用顺序[重复]
【发布时间】:2013-07-22 07:05:52
【问题描述】:
//: 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()"
    });  

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

【问题讨论】:

  • 但这不是你的功课吗?
  • @ChristianMark 这绝对不是家庭作业,我在阅读 Java 思维时自己也有同样的问题。我发现这个问题和答案非常有用:)

标签: java inheritance constructor


【解决方案1】:

构造函数:

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


关于你的第二部分问题:

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

这段代码创建了一个未命名的数组,同时初始化了一些字符串字面量。它类似于创建命名数组的方式:

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

【讨论】:

  • @user13267。不客气:)
  • 如果它们是原语而不是对象,情况会一样吗?例如,如果存在 int b = 5 int c =10 和 int l = 15,而 new Sandwich(); 没有被调用,编译器还会为 b、c 和 l 分配内存吗?
  • @user13267。好吧,如果你甚至不打电话给new Sandwitch()?在任何情况下都不会发生任何事情。实例字段只会在您创建类的实例时被初始化。通过new Sandwitch(),你实际上是在实例化你的类。
  • 我现在明白了。我似乎把 C++ 和 Java 搞混了。非常感谢。
  • @user13267。不客气:)
【解决方案2】:

您的示例中的对象使用继承,这会导致调用构造函数链。使用继承时,从另一个类 (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.

【讨论】:

  • 但是在 main 中注释掉 new Sandwich(); 使得它根本没有输出。这是为什么?面包、奶酪和生菜是独立的类。即使没有创建 Sandwich 的对象,是否应该调用其他字段的构造函数,因为它们已在 Sandwich 类中显式声明?
  • @user13267 但是它们在哪里实例化?
  • @user13267 那是因为没有实例化对象,所以永远不会调用所有构造函数,导致永远不会调用所有打印语句。基本上永远不会遵循打印出所有调试消息的执行路径。
【解决方案3】:

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

Bread()
Cheese()
Lettuce()

之后

Meal()
Lunch()
PortableLunch()

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

【讨论】:

    【解决方案4】:
    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
    

    【讨论】:

      猜你喜欢
      • 2015-02-11
      • 2012-02-28
      • 2013-01-29
      • 2011-11-24
      • 1970-01-01
      • 2019-05-16
      • 2019-01-15
      相关资源
      最近更新 更多