6: Reusing Classes(復(fù)用類)

第一種非常簡單:在新的類里直接創(chuàng)建舊的類的對象。這被稱為合成(compostion)。
第二種方法更為精妙。它會創(chuàng)建一個新的,與原來那個類同屬一種類型的類。你全盤接受了就類的形式,在沒有對它做修改的情況下往里面添加了新的代碼。這種神奇的做法被稱為繼承(inheritance)。

合成所使用的語法
如果你想對reference進行初始化,那么可以在以下幾個時間進行:
1. 在定義對象的時候。這就意味著在構(gòu)造函數(shù)調(diào)用之前,它們已經(jīng)初始化完畢了。
2. 在這個類的構(gòu)造函數(shù)里。
3. 在即將使用那個對象之前。這種做法通常被稱為“偷懶初始化(lazy initalization)”。如果碰到創(chuàng)建對象的代價很高,或者不是每次都需要創(chuàng)建對象的時候,這種做法就能降低程序的開銷了。

繼承所使用的語法
繼承設(shè)計方面有一條通用準則,那就是把數(shù)據(jù)都設(shè)成private的,把方法都設(shè)成public的。當(dāng)然碰到特殊情況還要進行調(diào)整,但是這還是一條非常有用的準則。
基類的初始化
構(gòu)造行為是從基類“向外”發(fā)展的,所以基類會在派生類的構(gòu)造函數(shù)訪問它之前先進行初始化。
帶參數(shù)的構(gòu)造函數(shù)
如果類沒有默認的構(gòu)造函數(shù)(也就是無參數(shù)的構(gòu)造函數(shù)),或者你要調(diào)用的基類構(gòu)造函數(shù)是帶參數(shù)的,你就必須用super關(guān)鍵詞以及合適的參數(shù)明確地調(diào)用基類的構(gòu)造函數(shù)。
對派生類構(gòu)造函數(shù)而言,調(diào)用基類的構(gòu)造函數(shù)應(yīng)該是它做的第一件事。
捕獲基類構(gòu)造函數(shù)拋出的異常
編譯器會強制你將基類構(gòu)造函數(shù)的調(diào)用放在派生類的構(gòu)造函數(shù)的最前面。

把合成和繼承結(jié)合起來
雖然編譯器會強制你對基類進行初始化,并且會要求你在構(gòu)造函數(shù)的開始部分完成初始化,但是它不會檢查你是不是進行了成員對象的初始化,因此你只能自己留神了。
確保進行妥善地清理
先按照創(chuàng)建對象的相反順序進行類的清理。(一般來說,這要求留著基類對象以供訪問。)然后調(diào)用基類的清理方法。
最好不要依賴垃圾回收器去做任何與內(nèi)存回收無關(guān)的事情。如果你要進行清理,一定要自己寫清理方法,別去用finalize()。
名字的遮蓋

用合成還是繼承
合成與繼承都能讓你將子對象植入新的類(合成是顯式的,繼承是隱含的)。
合成用于新類要使用舊類的功能,而不是其接口的場合。也就是說,把對象嵌進去,用它來實現(xiàn)新類的功能,但是用戶看到的是新類的接口,而不是嵌進去的對象的接口。因此,你得在新類里嵌入private得就類對象。
有時,讓用戶直接訪問新類的各個組成部分也是合乎情理的;這就是說,將成員對象定義成public。
繼承要表達的是一種“是(is-a)”關(guān)系,而合成表達的是“有(has-a)”關(guān)系。

protected
對用戶而言,它是private的,但是如果你想繼承這個類,或者開發(fā)一個也屬于這個package的類的話,就可以訪問它了。(Java的protected也提供package的權(quán)限。)
最好的做法是,將數(shù)據(jù)成員設(shè)成private的;你應(yīng)該永遠保留修改底層實現(xiàn)的權(quán)利,然后用protected權(quán)限的方法來控制繼承類的訪問權(quán)限。

漸進式的開發(fā)

上傳
為什么叫“上傳”?

把派生類傳給基類就是沿著繼承圖往上送,因此被稱為“上傳(upcasting)”。上傳總是安全的,因為你是把一個較具體的類型轉(zhuǎn)換成較為一般的類型。也就是說派生類是基類的超集(superset)。它可能會有一些基類所沒有的方法,但是它最少要有基類的方法。在上傳過程中,類的接口只會減小,不會增大。
合成還是繼承,再深討
運用繼承的時候,你應(yīng)該盡可能的保守,只有在它能帶來很明顯的好處的時候,你才能用。在判斷該使用合成還是繼承的時候,有一個簡單的辦法,就是問一下你是不是會把新類上傳給基類。如果你必須上傳,那么繼承就是必須的,如果不需要上傳,那么就該再看看是不是應(yīng)該用繼承了。

final關(guān)鍵詞
設(shè)計和效率
final的三種用途:數(shù)據(jù)(data),方法(method)和類(class)
final的數(shù)據(jù)
常量能用于下列兩個情況:
1. 可以是“編譯時的常量(compile-time constant)”,這樣就再也不能改了。
2. 也可以是運行時初始化的值,這個值你以后就不想再改了。
如果是編譯時的常量,編譯器會把常量放到算式里面;這樣編譯的時候就能進行計算,因此也就降低了運行時的開銷。在Java中這種常量必須是primitive型的,而且要用final關(guān)鍵詞表示。這種常量的賦值必須在定義的時候進行。
一個既是static又是final的數(shù)據(jù)成員會只占據(jù)一段內(nèi)存,并且不可修改。
對primitive來說,final會將這個值定義成常量,但是對于對象的reference而言,final的意思則是這個reference是常量。初始化的時候,一旦將reference連到了某個對象,那么它就再也不能指別的對象了。但是這個對象本身是可以修改的;Java沒有提供將某個對象作成常量的方法。(但是你可以自己寫一個類,這樣就可以把類當(dāng)作常量了。)這種局限性也體現(xiàn)在數(shù)組上,因為它也是一個對象。
空白的final的數(shù)據(jù)(Blank finals)
Java能讓你創(chuàng)建“空白的final數(shù)據(jù)(blank finals)”,也就是說把數(shù)據(jù)成員聲明成final的,但卻沒給初始化的值。碰到這種情況,你必須先進行初始化,在世用空白的final數(shù)據(jù)成員,而且編譯器會強制你這么做。不過,空白的final數(shù)據(jù)也提供了一種更為靈活的運用final關(guān)鍵詞方法,比方說,現(xiàn)在對象里的final數(shù)據(jù)就能在保持不變性的同時又有所不同了。
你一定得為final數(shù)據(jù)賦值,要么是在定義數(shù)據(jù)的時候用一個表達式賦值,要么是在構(gòu)造函數(shù)里面進行賦值。為了確保final數(shù)據(jù)在使用之前已經(jīng)進行了初始化,這一要求是強制的。
Final的參數(shù)
Java允許你在參數(shù)表中聲明參數(shù)是final的,這樣參數(shù)也變成final了。也就是說,你不能在方法里讓參數(shù) reference指向另一個對象了。
Final方法
使用final方法的目的有二。第一,為方法上“鎖”,進制派生類進行修改。第二個原因就是效率。
final和private
如果方法是private的,那它就不屬于基類的接口。它只能算是被類隱藏起來的,正好有著相同的名字的代碼。如果你在派生類里創(chuàng)建了同名的public或protected,或package權(quán)限的方法,那么它們同基類中可能同名的方法,沒有任何聯(lián)系。你并沒有覆寫那個方法,你只是創(chuàng)建了一個新的方法。
Final類
把整個類都定義成final的(把final關(guān)鍵詞放到類的定義部分的前面)就等于在宣布,你不會去繼承這個類,你也不允許別人去繼承這個類。
final類的數(shù)據(jù)可以是final的,也可以不是final的,這要由你來決定。無論類是不是final的,這一條都適用于“將final用于數(shù)據(jù)的”場合。但是,由于final類精致了繼承,覆寫方法已經(jīng)不可能了,因此所有的方法都隱含地變成final了。
小心使用final

初始化與類地裝載
第一次使用static數(shù)據(jù)的時候也是進行初始化的時候。裝載的時候,static對象和static代碼段會按照它們字面的順序(也就是在程序中出現(xiàn)的順序)進行初始化。當(dāng)然static數(shù)據(jù)只會初始化一次。
繼承情況下的初始化
1. 裝載程序。先裝載派生類,然后裝載基類。
2. 執(zhí)行“根基類(root base class)”。先是基類的static初始化,然后是派生類的static初始化。
3. 創(chuàng)建對象。首先,primitive都會被設(shè)成它們的缺省值,而reference也會被設(shè)成null。然后,調(diào)用基類的構(gòu)造函數(shù),再調(diào)用派生類的構(gòu)造函數(shù)。

總結(jié)

練習(xí)

「讀書筆記」Thinking in Java 3rd Edition - 7: Polymorphism