0

我有这个继承结构:

public abstract class Mom {
    int dummy;
    Mom() {
        dummy = 0;
    }
    Mom(int d) {
        this();
        dummy = d;
    }
}
public class Kid extends Mom {
    String foo;
    Kid() {
        super();
        foo = "";
    }
    Kid(int d) {
        super(d);
    }
}
// ...
Kid kiddo = new Kid(10);
// kiddo.foo == null !

我的无参数构造函数Kid永远不会被调用!这是我的预期:

  1. new Kid(10)Kid#Kid(int)
  2. super(d)Mom#Mom(int)
  3. this()Kid#Kid()// 哇!!
  4. super()Mom#Mom()

是否可以从Mom调用Kid的无参数构造函数?
我想它不是,我将添加一个抽象方法[1] init()Mom它将调用并且Kids必须覆盖。
但我只是想知道确切的原因,如果可能的话,例子证明为什么想要调用子类的构造函数是一个坏主意(即使子类的构造函数确实调用了super())。

// in Mom:
protected abstract void init();
public Mom() {
    dummy = 0;
    init();
}

// in Kid:
@Override
protected abstract void init() {
    foo = "";
}
4

3 回答 3

6

我会安排这些的方式,所以你不需要调用每个构造函数。

public abstract class Parent {
    final int dummy;

    Parent () {
        this(0);
    }
    Parent (int d) {
        dummy = d;
    }
}

public class Kid extends Parent {
    final String foo = "";

    Kid() {
    }

    Kid(int d) {
        super(d);
    }
}

使用final确保每个字段都设置一次。


从构造函数调用任何可覆盖的方法被认为是不好的做法,因此使构造函数可覆盖是一个坏主意。


this()调用同一类的构造函数,因为构造函数不遵循继承(静态方法也不遵循)

new Kid(10) --> Kid#Kid(int)
super(d) --> Mom#Mom(int)
this() --> Mom#Mom()

构造函数会这样做,否则您将面临多次调用同一个构造函数的危险,并且无法保证最终方法只设置一次。

于 2012-08-09T08:58:37.657 回答
2

来自JLS §8.8.7.1(我强调):

  • 备用构造函数调用以关键字this开头(可能以显式类型参数开头)。它们用于调用同一类的备用构造函数。

  • 超类构造函数调用以关键字super(可能以显式类型参数开头)或 Primary 表达式开头。它们用于调用直接超类的构造函数。

因此,this-constructor-invocation 总是引用同一个类,而不是子类。

虽然可以在构造函数中调用虚方法,但这是不安全的并且被认为是不好的做法,因为它可能导致这些方法使用部分初始化的对象实例。

对于您的问题,有几种可能的解决方案:

  1. 在声明时初始化成员foo,即foo = "";. 这也称为字段初始值设定项
  2. 使用实例初始化器{ foo = ""; }。请注意,如果您的类中需要,您可以拥有多个实例初始化程序。
  3. 咬紧牙关,在所有构造函数中重复初始化

根据JLS §12.5,(1) 和 (2) 中的初始化总是在构造函数本身被调用之前执行,因此您有一个定义良好的对象初始化,而无需诉诸有问题的模式。

如果一个成员被多次初始化,那么最后一次初始化获胜:

4) 执行该类的实例初始化器和实例变量初始化器,将实例变量初始化器的值分配给相应的实例变量,按照它们在源代码中以文本形式出现的从左到右的顺序。

如果在构造函数中也初始化了相同的字段,则构造函数获胜。

于 2012-08-09T09:21:57.353 回答
0

您应该有 Kid 类的这些构造函数:

Kid(int i) {
  super(i);
  whatever();
}

Kid () {
  this( DEFAULT_VALUE);
}

这样所有对父构造函数的调用都是通过子类的完全限定构造函数进行的。并且对于你的类的所有构造函数都有一个默认行为,它不会被绕过,就像你当前的代码一样。

于 2012-08-09T09:01:25.787 回答