从构造函数

时间:2015-05-29 22:58:17

标签: java constructor override

如果有可能请为我说明这句话

这里,作者说:

  

不要从构造函数中调用可覆盖的方法。创建时   子类对象,这可能导致被调用的方法被调用   在子类对象完全初始化之前。回想一下,当你   构造一个子类对象,它的构造函数首先调用其中一个   直接超类的构造函数。 如果是超类构造函数   调用一个可覆盖的方法,该方法的子类的版本   将由超类构造函数调用 - 在子类之前   构造函数的主体有机会执行。

我无法理解如何在超类的构造函数中调用子类的可覆盖方法

TNX

3 个答案:

答案 0 :(得分:3)

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

采用以下类型层次结构:

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?

答案 1 :(得分:2)

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

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类),NamedGreeter中的实现将被调用。从父类的构造函数中调用该方法意味着即使子类扩展它,也不会初始化子项定义的字段,因为它不可能在子构造函数中执行任何操作(如初始化)字段)在调用父构造函数之前。

答案 2 :(得分:1)

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

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)