简体   繁体   English

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

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

I must have spent over an hour trying to figure out the reason for some unexpected behavior. 我一定花了一个多小时试图找出出现意外行为的原因。 I ended up realizing that a field wasn't being set as I'd expect. 我最终意识到没有像我期望的那样设置场地。 Before shrugging and moving on, I'd like to understand why this works like this. 在耸耸肩并继续前进之前,我想明白为什么会这样。

In running the example below, I'd expect the output to be true, but it's false. 在运行下面的示例时,我希望输出为true,但它是错误的。 Other tests show that I always get whatever that type default value is. 其他测试表明,我总是得到任何类型的默认值。

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();
    }
}

output: 输出:

bool=false
boolean bool = true;

public ClassTwo() {
    super();
}

is identical to 是完全相同的

boolean bool;

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

The compiler automatically moves the fields initializations within the constructor (just after the super constructor call, implicitly or explicitly). 编译器自动在构造函数中移动字段初始化(在超级构造函数调用之后,隐式或显式)。

Since a boolean field default value is false , when super() is called (and thus ClassOne() and fireMethod() ), bool hasn't been set to true yet. 由于布尔字段默认值为false ,因此调用super() (因此ClassOne()fireMethod() )时, bool尚未设置为true


Fun fact: the following constructor 有趣的事实:以下构造函数

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

will be understood as 将被理解为

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

by the JVM, and the output will thus be 由JVM,因此输出将是

bool=false
bool=true

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). 在Java中,在运行构造函数之前,所有实例成员都有其默认值(false,0,null)。 So when super() is called, bool is still false (default value for booleans). 所以当调用super()时, bool仍然是假的(布尔值的默认值)。

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

The instance initializers are executed after super() is called implicitly or explicitly. 在隐式或显式调用super()之后执行实例初始值设定项。

From the Java Language Specification, section 12.5: "Creation of new class instances : 从Java语言规范, 第12.5节:“创建新的类实例

"3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4. “3.此构造函数不是以同一个类中的另一个构造函数的显式构造函数调用开始的(使用此方法)。如果此构造函数用于Object以外的类,则此构造函数将以显式或隐式调用超类开始构造函数(使用super)。使用这五个相同的步骤评估参数并递归处理超类构造函数调用。如果构造函数调用突然完成,则此过程会因同样的原因突然完成。否则,继续执行步骤4。

"4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5." “4.为此类执行实例初始值设定项和实例变量初始值设定项,将实例变量初始值设定项的值分配给相应的实例变量,按从左到右的顺序,它们以文本方式显示在类的源代码中。执行任何这些初始值设定项都会导致异常,然后不再处理其他初始值设定项,并且此过程会突然完成同样的异常。否则,继续执行步骤5。

super() is actually superfluous (pun not intended) in this case because it is implicitly called in every constructor. 在这种情况下, super()实际上是多余的(双关语),因为它在每个构造函数中被隐式调用。 So what this means is that the constructor of ClassOne is called first. 所以这意味着首先调用ClassOne的构造函数。 So before a constructor is run, the instance members have their default values (so bool is false ). 因此,在运行构造函数之前,实例成员具有其默认值(因此boolfalse )。 It is only after the constructors are run, that the fields are initialized. 只有构造函数运行之后,才会初始化字段。

So your constructor effectively becomes: 所以你的构造函数有效地成为:

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

    bool = true; //initialize members;
}

But ClassOne calls the overridable method that prints out the value of bool , which is false at that point. ClassOne调用可ClassOne的方法,该方法打印出bool的值,此时该值为false

In general, it is bad practice to call overridable methods from a constructor (as you are doing in ClassOne ) because you're now working with an object that is not completely initialized. 通常,从构造函数中调用可覆盖的方法(就像在ClassOne )是不好的做法,因为您现在正在处理未完全初始化的对象。

From Effective Java (2nd Edition): 来自Effective Java(第2版):

There are a few more restrictions that a class must obey to allow inheritance. 为了允许继承,类必须遵守一些限制。 Constructors must not invoke overridable methods, directly or indirectly. 构造函数不得直接或间接调用可覆盖的方法。 If you violate this rule, program failure will result. 如果违反此规则,将导致程序失败。 The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will be invoked before the subclass constructor has run. 超类构造函数在子类构造函数之前运行,因此在子类构造函数运行之前将调用子类中的重写方法。 If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected. 如果重写方法依赖于子类构造函数执行的任何初始化,则该方法将不会按预期运行。

The final answer will be: do not use an overridable method in a constructor. 最后的答案是:不要在构造函数中使用可覆盖的方法。

In every constructor: 在每个构造函数中:

  • call the super constructor (implicit or first in the constructor) 调用超级构造函数(隐式或构造函数中的第一个)
  • do the field initialisations of the current class ( type field = value; ) 进行当前类的字段初始化( type field = value;
  • do the rest of the constructor 做其余的构造函数

This makes life interesting 这让生活变得有趣

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);
    }
}

This results in 这导致了

B.init a=0, b=0
B.f a=13, b=15
  1. As during the call of B.init B fields are the zeroed ones. 在B.init B的调用期间,字段是归零的。
  2. It will set a to 7 (for nothing), and b to 15. 它将a设置为7(无效), b为15。
  3. Then in B's constructor a is initialized. 然后在B的构造a被初始化。

My IDE already flags calling an overridable method in a constructor as bad style. 我的IDE已经将构造函数中的可覆盖方法调用为坏样式。

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

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