面向對象的三個基本特徵:數據抽象、繼承和多態。多態的做用是消除類型之間的耦合關係。多態也被稱做動態綁定、後期綁定或運行時綁定。編程
8.2安全
將一個方法調用同一個方法主體(我理解就是方法和它的對象)關聯起來被稱做綁定。若在程序執行前進行綁定(通常由編譯器和鏈接程序實現)叫作前期綁定。C是前期綁定的。編程語言
後期綁定是在運行時根據對象的類型進行綁定,也叫作動態綁定或運行時綁定。若是一種語言想實現後期綁定,就必須具備某種機制,以便在運行時能判斷對象的類型,從而喬勇恰當的方法。編譯器一直不知道對象的類型,可是方法調用機制可以找到正確的方法體,並加以調用。後期綁定機制隨編程語言的不一樣而有所不一樣,但無論怎樣都必須在對象中安置某種類型信息。ide
Java中除了static和final方法(private是隱式的final)以外,其餘全部的方法都是後期綁定。聲明一個方法爲final,能夠防止該方法被覆蓋,也能夠有效的關閉動態綁定,或者說告訴編譯器不須要對其進行動態綁定。這樣編譯器就能夠爲final方法調用生成更有效的代碼。函數
有了動態綁定,就能夠編寫只與基類打交道的代碼,而且這些代碼對全部導出類均可以正確運行。測試
練習6中最後括號中有一句話:不用向上轉型。這句話有點費解,本節說的就是多態的向上轉型,不用幹什麼呢?查了一下英文原版括號裏:without any casting. 此處應該理解爲不用手動轉型吧?編碼
練習10值得注意:一個基類有方法1和方法2,方法1中調用方法2,導出類覆寫了方法2。建立一個導出類,並向上轉型到基類,而後調用方法1。此時方法1中調用的是導出類中覆寫的方法2。spa
8.2最後兩小節列出了兩個容易被忽視的問題,第一段例子:對象
表面上看,好像導出類的f()方法覆寫了基類的,可是其實基類的f()方法是private,因此是隱式的final。因此此處並無發生覆寫,而基類的引用調用的天然是基類的方法。這種問題能夠用@Override註解來解決,當沒有發生覆寫時使用了該註解,編譯器會報錯。並且此種狀況出現的機率並不大,由於基類的f()方法是private的,很難在類以外的其餘地方調用。繼承
第二個例子是關於域的:
域(或者字段)的訪問操做都是由編譯器解析,因此不是多態的。什麼類型的引用調用的域就是什麼類型對象中的域。此處例子能夠看到將域設置爲private,而後用get方法獲取域的一個好處,它不會讓人產出迷惑。不過最好仍是不要把導出類中的域和基類中的域用相同的名字命名。
第三個例子關於靜態函數,它是隨類而不隨對象走的,因此也沒有多態機制,這個例子比較好理解,並且通常也不用對象的引用去調用static函數(用類名調用),因此不作過多解釋:
8.3
複雜對象調用構造函數的順序:
(0)將分配給對象的存儲空間初始化成二進制的零;
(1)調用基類的構造函數。整個步驟會不斷的反覆遞歸下去,首先是構造整個層次結構的根,而後是下一層導出類,直到最底層的導出類;
(2)按聲明順序調用成員的初始化方法;
(3)調用導出類構造函數的主體。
做者在本小節的最後說明了一種在構造函數中調用覆寫方法的狀況,這種狀況會致使導出類中覆寫函數被調用,因爲導出類中的域尚未初始化,會形成一些難以發現的錯誤:
編寫構造函數時有一條有效的準則:用盡量簡單的方法使對象進入正常狀態;若是能夠的話,避免調用其餘方法。在構造函數內惟一可以安全調用的方法是基類中的final方法(也適用於private,由於它是隱式final)。
8.4
協變返回類型:從1.5開始引入,導出類中的覆蓋方法能夠返回基類方法返回類型的某種導出類型(導出類中的覆蓋方法的返回類型能夠比基類方法返回類型更具體)。本身編碼測試過,的確如此,從前從未注意過這個特性。
8.5
關於組合和繼承,做者的建議是,首選組合,尤爲是不能十分肯定應該使用哪種方式時。還有一條通用的準則:「用繼承來表達行爲間的差別,用字段表達狀態上的變化。」
做者用了大概一頁的篇幅來探討繼承中純繼承和擴展繼承的優劣。
純繼承是一種is-a關係,導出類的接口與基類接口徹底相同,所以能夠全程利用多態和向上轉型。
擴展繼承是一種is-like-a關係,導出類比基類擁有更多的功能(更多的接口),這樣程序實現起來更靈活,並且貌似更符合Java開發者的本意(由於繼承用了關鍵字extends)。可是在向上轉型時就會丟失導出類那些新增的功能。
對兩個繼承方式優劣的思考,不得不說是一個哲學問題,孰優孰劣很難說的清楚。
Java中,全部轉型都會檢查,在進入運行時仍會檢查,若是不是該類型,就會拋出ClassCastException異常。運行期間對類型進行檢查的行爲稱做「運行時類型識別(RTTI)」。