从构造函数调用可覆盖方法的问题

the issues of calling overridable methods from constructor

如果可以请帮我把这句话说清楚

这里作者说:

Do not call overridable methods from constructors. When creating a subclass object, this could lead to an overridden method being called before the subclass object is fully initialized. Recall that when you construct a subclass object, its constructor first calls one of the direct superclass’s constructors. If the superclass constructor calls an overridable method, the subclass’s version of that method will be called by the superclass constructor—before the subclass constructor’s body has a chance to execute.

我无法理解如何在超类的构造函数中调用子类版本的可重写方法

TnX

演示将调用子方法的示例:

class Foo {
  static class Parent {
    Parent() {
      someMethod();
    }
    void someMethod() {}
  }

  static class Child extends Parent {
    @Override void someMethod() {
      throw new AssertionError("Invoked");
    }
  }

  public static void main(String[] args) {
    new Child();  // Throws Exception.
  }
}

输出:

Exception in thread "main" java.lang.AssertionError: Invoked
        at Foo$Child.someMethod(Foo.java:16)
        at Foo$Parent.<init>(Foo.java:9)
        at Foo$Child.<init>(Foo.java:14)
        at Foo.main(Foo.java:21)

为了说明使用某些(简单的)代码这是一个坏主意的原因,请考虑以下两个 classes:

class Greeter {
    protected Greeter() {
        printHello();
    }

    protected void printHello() {
        System.out.println("Hello");
    }
}

看起来很简单,只要你实例化它就会打印 Hello 。现在让我们扩展它:

class NamedGreeter extends Greeter {
    private String name;

    public NamedGreeter(String name) {
        this.name = name;
    }

    @Override
    protected void printHello() {
        System.out.println("Hello " + name);
    }
}

意图显然是让 NamedGreeter 在实例化时通过名字问候你,但实际上它总是打印 Hello null 因为当 NamedGreeter 时超级构造函数首先被调用被实例化。

由于多态性的工作方式,任何时候 printHello() 方法在 NamedGreeter 上被调用(即使该调用是从 Greeter class 中调用的) NamedGreeter 中的实现将被调用。从父 class 的构造函数中调用该方法意味着即使子 class 扩展它,也不会初始化子定义的任何字段,仅仅是因为不可能在子中做任何事情调用父构造函数之前的构造函数(如初始化字段)。

你首先要区分实例化和初始化。实例化是创建类型实例的过程(为其分配 space 并获取对该 space 的引用)。初始化是将实例的状态设置为其初始值的过程。

采用以下类型层次结构:

class Foo { 
    public Foo() {}
}
class Bar extends Foo {
    public Bar() {super();}
}

新实例创建表达式

new Bar();

导致实例化和初始化。实例化首先发生。 Java 创建具体类型的实例 Bar.

然后需要进行初始化。在继承层次结构中,初始化遵循相同的层次结构,但从上到下。

Object
   |
  Foo 
   |
  Bar

Object 的构造函数首先运行以初始化定义为 Object 的一部分的状态,然后 Foo 的构造函数是 运行 初始化状态被定义为 Foo 的一部分,最后 Bar 的构造函数是 运行 初始化 Bar 中定义的状态。 您的实例仍然是 Bar. 类型,因此多态性仍然适用。如果您调用一个实例方法,并且该方法在层次结构的较低位置被重写,则将调用该实现。

这就是那句话所指的。这很危险。在这里阅读更多内容:

What's wrong with overridable method calls in constructors?