重寫方法的問題,如果有可能,請讓這句話清楚我調用構造函數從
這裏,筆者說:
不要從構造函數重寫的方法。創建子類對象時,可能會導致在子類對象完全初始化之前被重寫的方法稱爲 。回想一下,當你構造一個子類對象時,它的構造函數首先調用 直接超類的構造函數之一。 如果超類的構造函數 調用一個可重寫的方法,該方法的子類的版本 將由超類構造函數調用 - 在子類 構造函數的主體有機會執行之前。
我不能明白,它如何能夠調用重寫方法的子類的版本在父類的構造函數
TNX
重寫方法的問題,如果有可能,請讓這句話清楚我調用構造函數從
這裏,筆者說:
不要從構造函數重寫的方法。創建子類對象時,可能會導致在子類對象完全初始化之前被重寫的方法稱爲 。回想一下,當你構造一個子類對象時,它的構造函數首先調用 直接超類的構造函數之一。 如果超類的構造函數 調用一個可重寫的方法,該方法的子類的版本 將由超類構造函數調用 - 在子類 構造函數的主體有機會執行之前。
我不能明白,它如何能夠調用重寫方法的子類的版本在父類的構造函數
TNX
您必須首先區分實例化和初始化。實例化是創建類型實例(爲其分配空間並獲取對該空間的引用)的過程。初始化是將實例的狀態設置爲其初始值的過程。
採取以下類型層次:
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:
一個例子,證明孩子的方法將被調用:
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)
爲了說明這是一個壞主意了一些(簡單的)代碼的原因,考慮這兩個類:
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
實施將被調用。在父類的構造函數中調用該方法意味着即使子類繼承它,也不會初始化子類定義的任何字段,僅僅是因爲在子類構造函數之前無法在子構造函數中執行任何操作(如初始化字段)父構造函數被調用。