繁体   English   中英

当从super()运行方法时,为什么字段没有初始化为非默认值?

[英]Why aren't fields initialized to non-default values when a method is run from super()?

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

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

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
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

在子类构造函数之前调用超类构造函数。 在Java中,在运行构造函数之前,所有实例成员都有其默认值(false,0,null)。 所以当调用super()时, bool仍然是假的(布尔值的默认值)。

更一般地说,从构造函数(在ClassOne中)调用可覆盖的方法是一个坏主意,因为您刚刚发现:您最终可能还在处理尚未完全初始化的对象。

在隐式或显式调用super()之后执行实例初始值设定项。

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

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

“4.为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值分配给相应的实例变量,按从左到右的顺序,它们以文本方式显示在类的源代码中。执行任何这些初始值设定项都会导致异常,然后不再处理其他初始值设定项,并且此过程会突然完成同样的异常。否则,继续执行步骤5。

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

所以你的构造函数有效地成为:

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

    bool = true; //initialize members;
}

ClassOne调用可ClassOne的方法,该方法打印出bool的值,此时该值为false

通常,从构造函数中调用可覆盖的方法(就像在ClassOne )是不好的做法,因为您现在正在处理未完全初始化的对象。

来自Effective Java(第2版):

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

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

在每个构造函数中:

  • 调用超级构造函数(隐式或构造函数中的第一个)
  • 进行当前类的字段初始化( 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的调用期间,字段是归零的。
  2. 它将a设置为7(无效), b为15。
  3. 然后在B的构造a被初始化。

我的IDE已经将构造函数中的可覆盖方法调用为坏样式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM