8

我一定花了一个多小时试图找出一些意外行为的原因。我最终意识到没有像我期望的那样设置一个字段。在耸耸肩继续前进之前,我想了解为什么会这样。

在运行下面的示例时,我希望输出为真,但它是假的。其他测试表明,我总是得到该类型默认值。

public class ClassOne {

    public ClassOne(){
        fireMethod();
    }

    protected void fireMethod(){
    }

}

public class ClassTwo extends ClassOne {

    boolean bool = true;

    public ClassTwo() {
        super();
    }

    @Override
    protected void fireMethod(){
        System.out.println("bool="+bool);
    }

    public static void main(String[] args) {
        new ClassTwo();
    }
}

输出:

bool=false
4

5 回答 5

7
boolean bool = true;

public ClassTwo() {
    super();
}

等同于

boolean bool;

public ClassTwo() {
    super();
    bool = true;
}

编译器自动移动构造函数中的字段初始化(就在超级构造函数调用之后,隐式或显式)。

由于布尔字段默认值是false,当super()被调用时(因此ClassOne()fireMethod()),bool尚未设置为true


有趣的事实:以下构造函数

public ClassTwo() {
    super();
    fireMethod();
}

会被理解为

public ClassTwo() {
    super();
    bool = true;
    fireMethod();
}

由JVM,因此输出将是

bool=false
bool=true
于 2013-07-15T15:32:22.873 回答
5

The superclass constructor is called before the subclass constructor. And in Java, before a constructor is run, all instance members have their default value (false, 0, null). So when super() is called, bool is still false (default value for booleans).

More generally, calling an overridable method from a constructor (in ClassOne) is a bad idea for the reason you just discovered: you might end up working on an object that has not been fully initialised yet.

于 2013-07-15T15:29:32.580 回答
1

实例初始化器在 super() 被隐式或显式调用后执行。

来自 Java 语言规范,第 12.5 节:“创建新类实例

"3. 此构造函数不以显式调用同一类中的另一个构造函数开始(使用 this)。如果此构造函数用于 Object 以外的类,则此构造函数将以显式或隐式调用超类开始构造函数(使用 super)。使用这五个相同的步骤递归地评估参数并处理超类构造函数调用。如果该构造函数调用突然完成,则此过程出于相同的原因突然完成。否则,继续第 4 步。

"4. 执行该类的实例初始化器和实例变量初始化器,将实例变量初始化器的值分配给相应的实例变量,按照它们在类源代码中以文本形式出现的从左到右的顺序。如果执行这些初始化程序中的任何一个都会导致异常,然后不再处理任何初始化程序,并且此过程会突然完成并出现相同的异常。否则,继续执行步骤 5。

于 2013-07-15T15:29:49.227 回答
0

super()在这种情况下实际上是多余的(双关语不是有意的),因为它在每个构造函数中都被隐式调用。所以这意味着ClassOne首先调用的构造函数。所以在构造函数运行之前,实例成员有它们的默认值(所以boolfalse)。只有构造函数运行之后,字段才会被初始化。

因此,您的构造函数有效地变为:

public ClassTwo() {
    super(); //call constructor of super class

    bool = true; //initialize members;
}

但是ClassOne调用打印出 的值的可覆盖方法,boolfalse在该点。

一般来说,从构造函数调用可覆盖的方法(就像在 中所做的那样ClassOne)是不好的做法,因为您现在正在使用未完全初始化的对象。

来自 Effective Java(第 2 版):

一个类必须遵守更多的限制以允许继承。构造函数不得直接或间接调用可覆盖的方法。如果您违反此规则,将导致程序失败。超类构造函数在子类构造函数之前运行,因此子类中的覆盖方法将在子类构造函数运行之前被调用。如果覆盖方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

于 2013-07-15T15:31:25.093 回答
0

最终答案将是:不要在构造函数中使用可覆盖的方法。

在每个构造函数中:

  • 调用超级构造函数(隐式或构造函数中的第一个)
  • 做当前类的字段初始化 ( type field = value;)
  • 做其余的构造函数

这让生活变得有趣

public class A {

    public A() {
        init();
    }

    protected void init() {
    }
}

public class B extends A {
    int a = 13;
    int b;

    @Override
    protected void init() {
        System.out.println("B.init a=" + a + ", b=" + b);
        a = 7;
        b = 15;
    }

    public static void main(String[] args) {
        new B().f();
    }

    public void f() {
        System.out.println("B.f a=" + a + ", b=" + b);
    }
}

这导致

B.init a=0, b=0
B.f a=13, b=15
  1. 在调用 B.init B 期间,B 字段是归零的。
  2. 它将设置a为 7(一无所有)和b15。
  3. 然后在B的构造函数a中初始化。

我的 IDE 已经将在构造函数中调用可覆盖方法标记为错误样式。

于 2013-07-15T15:44:17.103 回答