【问题标题】:Why does a sub-class call super() by default?为什么子类默认调用super()?
【发布时间】:2012-12-19 22:00:31
【问题描述】:

这个问题一直困扰着我一段时间,我还没有找到一个好的答案(除了“就是这样”)。

让我给出一些背景代码,以说明我在说什么。

class Note {
    private final String name = "Note";

    public Note() {
        System.out.println(name);
    }
    // ...
}

class Todo extends Note {
    private final String name = "Todo";

    public Todo() {
        System.out.println(name);
    }
    // ...
}

// ...
Note note = new Todo(); // case 1
Todo todo = new Todo(); // case 2

那么案例 1 和案例 2 怎么会打印出来:

Note
Todo

这是没有意义的,因为Todo()(构造函数)不调用super()(至少不可见)。为什么子类必须调用父类的默认构造函数,为什么不要求任何子类实现构造函数?

我读了几篇与此相关的questions,但没有人回答为什么

编辑:
我想我的例子有点糟糕,但它实际上是 Java 7 认证问题的派生词。从答案的集合中,我现在明白了为什么。让我举一个更好的例子:

public Note {
    private String description;

    public Note() {
        description = "I'm a Note";
    }

    public Note( String description ) {
        this.description = description;
    }

    // getters/setters/etc.
}

public Todo extends Note {
    // field vars..

    public Todo() {
        // empty constructor
    }

    // getters/setters/etc..
}

所以现在这更有意义了,因为当 Todo 被创建时,如果 super() 没有被插入到封面后面,TodoNote 方面将不会被初始化。在这种情况下,拥有一个子类是没有意义的。谢谢大家!

【问题讨论】:

  • 对我来说,它会打印出Note Todo Note Todo,这是有道理的,因为您在这两种情况下都按顺序创建了NoteToDo
  • 我确实提到那是输出,但问题是:为什么默认调用父类的默认构造函数。
  • 因为每个对象都必须构造! (如Every Sperm is Sacred

标签: java oop constructor subclass


【解决方案1】:

子类构造函数必须调用一些构造函数。如果你没有告诉它使用哪个,它将使用默认构造函数。

另一种方法是让超类中的变量完全未初始化,但可以由子类和/或其方法访问,这将是非常糟糕

决定静默调用默认超类构造函数,而不是例如编译失败,虽然是有争议的,但我怀疑它首先与“默认构造函数”的存在有关。

仅供参考,您的问题还表明您可能对继承感到困惑。 Note.nameTodo.name 字段完全分开:您不能在子类中覆盖 字段,只能覆盖方法。

【讨论】:

  • 关于仅供参考,我知道字段不可访问,除非 public 甚至可能是 public final,所以只能读取访问权限。
  • 可访问性和继承是两个不同的东西。 Todo 类有两个不同的字段,命名为name,如当前所写。
  • @Knownasilya 这并不是说它们不可访问,实际上您将拥有两个单独的字段,称为name。您也可以使用protectedpublic 和非最终字段来执行此操作,这可能会导致混乱。查看更多here
  • 我明白,但在这种情况下,Note 类的 name 字段是 private,并且对 Todo 不可用/不可访问。不过我明白你的意思:)
【解决方案2】:

问题可以分为两部分。

  1. 为什么必须调用超类的构造函数?这很简单:构造函数负责设置对象的状态,并且由于子类代码不可见超类中的状态(即private字段),这只能通过调用构造函数来处理超类的。
  2. 为什么您不必明确地调用super()?这只是在 Java 中做出的一个相当随意的设计决定,以使代码看起来更简单。它与 default constructor 的概念很好地结合在一起(即存在于未定义显式构造函数的类中的隐含无参数构造函数),尽管从您的示例中可以看出,当您有在超类中显式定义的无参数构造函数。

【讨论】:

    【解决方案3】:

    您不必显式调用超类构造函数。并且没有必要在子类构造函数中调用 super() 或 super(arguments)。如果您不指定,编译器将自动添加对无参数超类构造函数的调用。

    如果超类没有无参数构造函数,则必须在每个子类构造函数中显式调用 super(arguments)。

    【讨论】:

      【解决方案4】:

      这两种情况都打印出 Note 和 Todo 的原因是你总是使用 operator new 创建类 Todo 的实例。如果你写过

      Note note = new Note();
      Note todo = new Todo(); 
      

      那么结果是:

      Note
      Note
      Todo
      

      该行为的原因是Polymorphism

      要回答为什么构造函数必须调用超级构造函数来创建自己很容易想象。由于它使用该超类的元素,因此必须首先创建该类,因此继承它的类可以对其进行操作。

      【讨论】:

        【解决方案5】:

        在您的示例中,父级的构造函数对对象不做任何事情。

        通常,构造函数通过设置字段来构造对象。这些字段对于该类的构造函数是已知的,并且可能无法在另一个类中设置或访问,并且您不希望在每个子类(已经在父类中)中复制所有代码,这样父字段设置正确。

        因此,对于每个类来说,初始化它所知道的字段是有意义的,而不是过多地依赖子类的行为来正确运行。这允许您更改一个类而无需更改它的子类。例如添加或更改字段。

        【讨论】:

          【解决方案6】:

          事实上,确实如此。 super() 调用会自动添加,即使您不编写它也是如此。这是 Todo 构造函数中的第一条指令。

          要自己测试,只需创建一个带有参数的新 Note 构造函数并删除空的。看看现在会发生什么:)

          为什么会发生这种情况?嗯,很简单:因为这就是它的实现方式。

          【讨论】:

            【解决方案7】:

            documentation 说明了一切:

            如果构造函数没有显式调用超类构造函数, Java 编译器自动插入对无参数的调用 超类的构造函数。如果超类没有 没有参数的构造函数,你会得到一个编译时错误。目的 确实有这样的构造函数,所以如果 Object 是唯一的超类, 没有问题。

            【讨论】:

              【解决方案8】:

              就是这样! :)

              对我来说,这一切都很有意义,因为您在面向对象编程范式中编写代码的主要原因是重用现有代码。

              有很多方法可以重用,其中之一就是继承一个类(即:创建一个子类)。

              当您继承一个类时,您也继承了该类的创建方式。

              在我看来,如果不存在这种行为,Java 将是一种更加令人困惑的语言

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-09-19
                • 1970-01-01
                • 1970-01-01
                • 2013-06-20
                • 1970-01-01
                • 2014-11-07
                • 2017-04-01
                • 1970-01-01
                相关资源
                最近更新 更多