1. 動態綁定
若子類SubClass覆蓋了父類SuperClass的某個非final非private方法f1()
即使子類被聲明為
SuperClass child = new SubClass();
使用child.f1()時仍然訪問子類的f1()
2. 如果把某個方法聲明為final,可以防止其他人覆蓋該方法。更為重要的一點是,這樣做可以有效的關閉動態綁定,從而生成更有效的代碼。然而,大多數情況下,這樣做對程序的整體性能不會有什么改觀。因此并不提倡為了僅僅提高性能而使用final。
3. private方法無法被覆蓋,但還是要注意覆蓋private方法的現象,上面的例子中,如SuperClass中含有private方法f2(),而子類中也聲明了同名的方法f2(),子類被聲明為
SuperClass child = new SubClass();
child.f2()時訪問了父類的f2()
4. 構造器和多態
構造器基本順序:
1)調用基類構造器(遞歸過程)
2)按聲明順序調用成員的初始化方法。
3)調用導出類構造器的主體。
5. 繼承與清理
通過組合與京城方法來創建新類時,不必擔心對象的清理問題。如果確實遇到清理的問題,那么必須為新類創建dispose()方法。當覆蓋被繼承類的dispose()方法時,務必記住調用基類的dispose()方法,也要注意清理順序。
6. 構造器內部的多態方法的行為
如果要調用構造器內部的一個動態綁定的方法,就要用到那個方法的被覆蓋后的定義。然而,產生的效果可能相當難于預料,并且可能造成一些難于發現的隱藏錯誤。
在任何構造器的內部,整個對象可能只是部分形成,然而一個動態綁定的方法調用卻會向外深入到繼承層次結構內部,它可以調用導出類的方法。如果在構造器里這么做,那么就可能會調用某個方法,而這個方法所操縱的成員可能還未初始化--這肯定會出問題。
import com.bruceeckel.simpletest.*;
abstract class Glyph {
abstract void draw();
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println(
"RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
System.out.println(
"RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
最后顯示
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
實際應用中,或許這會導致RoundGlyph對象生成后只畫了一個點,而不是預期的半徑為1的圓。
解釋:初始化的實際過程是:
1)將分配給對象的儲存空間初始化為二進制的0。
2)調用基類構造器。即此時調用了draw()方法,但radius仍然為0。
3)按照聲明的順序調用成員的初始化方法。
4)調用導出類的構造器主體。
邏輯方法已經十分完美,但行為卻會出錯(這種情況下,C++會產生更合理的行為)
因此,編寫構造器有一條有效的準則:用盡可能簡單的方法使對象進入正常狀態;如果可以的話,避免調用其他方法。在構造器內唯一能夠安全調用的那些方法是基類中的final方法(包括private方法),這些方法不能被覆蓋,也就不會出現上述問題。
7. 依然是繼承/組合的選擇
一條通用的準則是,用繼承表達行為間的差異,用字段表達狀態上的變化。
8. 純繼承:只有在基類或接口中已經建立的方法才可以在導出類中被覆蓋,基類與導出類的接口相同。
9. 向下轉型