2015-05-29 231 views
1

重写方法的问题,如果有可能,请让这句话清楚我调用构造函数从

这里,笔者说:

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

我不能明白,它如何能够调用重写方法的子类的版本在父类的构造函数

TNX

回答

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所以多态性仍然适用。如果调用一个实例方法,并且该方法在层次结构中较低的地方被覆盖,那么该实现将被调用。

这就是引用的意思。这很危险。 Read more here:

What's wrong with overridable method calls in constructors?

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