《java編程思想》多態與介面
- 2020 年 4 月 5 日
- 筆記
向上轉型
定義:把某個對象的引用視為對其基類類型的引用的做法被稱為向上轉型
方法調用綁定
將一個方法調用同一個方法主體關聯起來被稱作綁定。
前期綁定:程式執行前進行的綁定叫做前期綁定,前期綁定也是java中默認的綁定方式
後期綁定(動態綁定或運行時綁定):在運行時根據對象的類型進行綁定。在java中除了static方法和final方法之外,其他所有的方法都是後期綁定,也就是說,通常情況下,我們不用判斷是否應該進行後期綁定,它會自動發生。
構造器和多態
構造器調用順序:
(1) 調用基類構造器,此步驟會不斷反覆遞歸下去,首先是構造這種層次結構的根,然後是下一層導出類,一直到最低層的導出類
(2) 按聲明順序調用成員的初始化方法。
(3) 調用導出類構造器的主體
父類(靜態變數、靜態初始化塊)>子類(靜態變數、靜態初始化塊)>
父類(變數、初始化塊)>父類構造器>子類(變數、初始化塊)>子類構造器。(變數和初始化塊按定義順序初始化)
構造器內部的多態方法的行為
構造器調用的層次結構帶來一個有趣的兩難問題,如果在一個構造器的內部調用正在構造的對象的某個動態綁定方法,會發生什麼情況呢?眾所周知,在一般的方法內部,動態綁定的調用是在運行時才決定的,因為對象無法知道它是屬於方法所在的類,還是屬於那個類的導出類。如果調用構造器內部的一個動態綁定方法,就要用到那個方法的被覆蓋後的定義。這個調用的效果相當難預料,因為被覆蓋的方法在對象被完全構造之前就會被調用,這可能會造成一些難於發現的錯誤。
1 public class Test { 2 3 public static void main(String[] args) { 4 new RoundGlyph(5); 5 } 6 } 7 class Glyph{ 8 void draw(){ System.out.println("Glyph.draw()"); } 9 Glyph(){ 10 System.out.println("Glyph before draw()"); 11 draw(); 12 System.out.println("Glyph after draw()"); 13 } 14 } 15 class RoundGlyph extends Glyph{ 16 private int radius = 1; 17 RoundGlyph(int r){ 18 radius = r; 19 System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius); 20 } 21 void draw(){ System.out.println("RoundGlyph.draw(), radius = " + radius); } 22 }
程式碼運行結果:
結果是不是和你預想的結果不太一樣,嘿嘿,不用急,我們來分析一下程式碼是怎麼變成這樣的。首先在main方法中new了一個RoundGlyph類,然後類載入器發現它繼承了Glyph類,這時就會去載入Glyph類,運行Glyph類的構造方法,並在構造方法中調用draw()方法,但是這個方法在導出類中被覆蓋了,所以此時會調用子類的draw()方法,注意!關鍵來了,程式碼運行到這的時候只是載入了子類,並沒有給子類中的radius變數進行初始化,現在radius的值只是java默認分配的一個初始值,當父類運行draw()方法時,它的值只是初始值,所以這個時候會輸出0。
因此我們得出一個驚喜的結論:當我們在基類的構造器內調用了某個方法,並且該方法被導出類所覆蓋,此時調用的是導出類內的方法而不是基類本身的方法。
對於前面的初始化順序我們應該更正一下:
(1) 在其他任何事物發生之前,將分配給對象的存儲空間初始化成二進位的零
(2) 調用基類構造器
(3) 按照聲明的順序調用成員的初始化方法
(4) 調用導出類的構造器主體
最後: 編寫構造器有一條有效的準則:用儘可能簡單的方法使對象進入正常狀態,如果可以的話,避免調用其他方法。
抽象類和抽象方法
抽象類:通用介面建立起一種基本形式,以此表示所有導出類的共同部分
抽象方法:僅有聲明而沒有方法體的方法。
包含抽象方法的類叫做抽象類,如果一個類包含一個或者多個抽象方法,那麼該類必須被限定為抽象的。
介面:interface關鍵字使抽象的概念更向前邁進了一步,abstract關鍵字允許人們在類中創建一個或多個沒有任何定義的方法,但是沒有提供任何相應的具體實現,這些實現是由此類的繼承者創建的。interface關鍵字產生一個完全抽象的類,它根本沒有提供任何具體實現,介面只提供了形式,不提供任何具體實現。