2

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

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

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()(constructor) 不调用super()(至少不可见)。为什么子类必须调用父类的默认构造函数,为什么不要求任何子类实现构造函数?

我读了几个与此相关的问题,但没有人回答为什么

编辑:
我想我的例子有点糟糕,但它实际上是 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()没有被插入到封面后面,则不会初始化的Note方面。Todo在这种情况下,拥有一个子类是没有意义的。谢谢大家!

4

8 回答 8

7

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

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

不过,静默调用默认超类构造函数而不是编译失败的决定是值得商榷的,但我怀疑它首先与“默认构造函数”的存在有关。

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

于 2012-12-19T22:03:59.803 回答
3

这个问题可以分为两部分。

  1. 为什么必须调用超类的任何构造函数?这很简单:构造函数负责设置对象的状态,而由于超类中可能存在子类代码不可见的状态(即private字段),这只能通过调用超类的构造函数来处理.
  2. 为什么不必super()显式调用?这只是在 Java 中做出的一个相当随意的设计决定,以使代码看起来更简单。它与 a 的概念很好地联系在一起default constructor(即,存在于没有定义显式构造函数的类中的隐含的无参数构造函数),尽管从您的示例中可以看出,它也适用于没有定义的类arg 构造函数在超类中显式定义。
于 2012-12-19T22:04:50.747 回答
1

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

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

于 2012-12-19T22:04:59.347 回答
1

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

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

那么结果将是:

Note
Note
Todo

这种行为的原因是多态性

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

于 2012-12-19T22:07:48.320 回答
1

在您的示例中,父级的构造函数不对对象执行任何操作。

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

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

于 2012-12-19T22:41:55.683 回答
0

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

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

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

于 2012-12-19T22:06:22.477 回答
0

文档说明了一切:

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

于 2012-12-19T22:07:43.187 回答
0

就是这样!:)

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

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

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

在我看来,如果不存在这种行为,Java 将是一种更加混乱的语言

于 2012-12-19T23:15:10.123 回答