2011年12月24日 星期六

《Practical Java》筆記


一. 一般技術

1.Java 的引數是以 by value 的方式傳遞

2.final 只能防止變數值改變。如果被宣告為 final 的變數是個 object reference ,那麼該 reference 不能被改變,必須永遠指向同一個物件。然而被指的那個物件可以隨意改變

3.array可支援基本型別、物件;不可自動改變大小;速度快
Vector只支援物件;可自動改變大小;速度慢

4.以多型(polymorphism)替代 instanceof。當你必須從某個型別非法轉型成另一個毫無關係的型別時,才使用 instanceof

5.一旦不再需要 object reference,就將它設為 null




二. 物件與相等性

1.當你宣告了基本資料型別(Primitive types)如 int,則表示你為它宣告了一份儲存空間;而當你宣告了外覆類別(Wrapper class)如 Integer,為 object reference,因此該 Integer 物件在 stack 中記錄的不是物件本身,而是物件的 reference。
object reference instance 變數的預設值是 null,primitive type instance 變數的預設值則取決於型別,如 boolean 預設值為 false

2.以 == 比較兩個 primitive types 是否完全相同,或測試兩個 object references 是否指向同一個物件;以 equals() 比較兩個物件內容是否一致

3.若你所使用的 class 並未實作 equals(),則先判斷 java.lang.Object 的預設函式碼是否可勝任(只會比較 object references 的相等性)。若無法勝任,就應在某個外覆類別(Wrapper class)或 subclass 中撰寫你自己的 equals()

4.當「class objects 相等與否」的校驗工作超過了「object reference 之間的單純比較」時,則該 class 就應提供一個 equals()。換句話說,若佔用不同記憶體空間的兩個物件也可被視為邏輯上相同時,則該 class 就應提供 equals()

5.equals() 的最佳實作方式,就是搭配 getClass(),後者可確保只有相同 class 所產生的物件才被視為相等。此外,equals() 應當查看參與比較的物件是否為同一個物件。equals() 不必對每一個屬性進行比較,只有那些攸關「相等性」的屬性才需要比較

6.一旦 java.lang.Object 以外的任何 base class 實作了 equals(),你就一定要呼叫 super.equals()。你仍然應寫出「instanceof 校驗」,並於必要時呼叫 super.equals()

7.只有在不得不「對 derived classes 物件與 base class 物件進行比較」的場合中,才可以使用 insrtanceof。而這種比較會出現「不對稱相等性」(asymmetric equality)




三. 異常處理

1.若發生異常,控制流(control flow)轉移到某個 catch 區段或 finally 區段,而那裡又發生新異常,你可以保存一個 list ,用以包含所有的異常。然後拋出一個異常,其中持有一個 reference 指向上述 list。如此一來,新異常的接受者就能擁有異常的全部資訊

2.你應該在一開始就設計好你的錯誤處理策略,而不是在開發週期的最後才添加異常的處理

3.當你給出 throws 子句的時候,要填寫得完整無缺,將函式可能拋出的所有異常通通列出,是良好的編程習慣

4.不要在 try 區段中發出對 return、break 或 continue 述句的呼喚。萬一無法避免,一定要確保 finally 的存在不會改變你函式的回傳值

5.將異常的使用限制在「出錯」場合和「失敗」場合。不要拿來用於流程控制,要將 try/catch 區段置於迴圈之外

6.當你在面對「出乎程式可預料」的行為時,才使用異常,不要每逢出錯就使用

7.你可以用 try/catch 區段把建構元包圍起來,以便處理任何可能的失敗

8.每當你撰寫可能引發異常的程式碼時,都必須問自己:(1)異常發生之時、(2)異常處理過後、(3)復入(reentered)這段程式碼時,會發生什麼事情?程式碼運轉還能夠正常嗎?




四. 效率

1.先把焦點放在設計、資料結構和演算法上,你應該忘掉小幅效率這種東西。過早的最佳化是一切錯誤的根源

2.不要依賴編譯期(compile-time)最佳化技術

3.String 用來表示那些建立後就不再改變的字元序列(character strings),換句話說,其物件是唯讀且不可變的(immutable)。StringBuffer 用來表示內容可變的字元序列,提供一些函式用來修改底部(underlying)字元序列的內容

4.若你想進行字串接合,StringBuffer 會優於 String。若你需要 String 提供的功能,則考慮使用 StringBuffer 完成字串接合,再轉換為 String

5.少用同步化(synchronization)

6.ArrayList 應當只用於「不需同步化」的時候

7.盡可能使用 stack 變數。如果你在迴圈中取用 static 變數或 instance 變數,你可以將它們暫時儲存於一個 local stack 變數中,以大幅提升程式效率

8.若每個函式被宣告為 static 或 final 或 private,其函式本體將被「inline化」,也就是被放進每個呼叫點,如此可在不明顯增加體積情況下改善執行效率。然而,這樣的函式無法透過 subclassing(子類化)進行擴展,也就束縛了 derived class 透過 class 函式做事情的機會

9.inline 函式只有在「被多次呼叫」的情況下,才會獲得明顯的效率提升

10.inlining 可能會使你的程式碼體積變大,如果這個函式有許多呼叫點,.class 檔案的體積就會膨脹,因為原本只需儲存一份的函式碼,inlined 會在所有呼叫點複製一份

11.local 變數沒有預設值,因此你必須明確將它初始化。只有 class 的 static 變數和 instance 變數才有預設值,你不必浪費效能再去初始化

12.創建基本型別(primitive types)比創建物件的程式碼更快更小

13.不要使用 Enumeration 或 Iterator 來巡訪 Vector

14.以 System.arraycopy() 來複製 arrays

15.array 的效率快過 ArrayList,而 ArrayList 快過 Vector。當你面對效率至為關鍵的程式任務時,使用 array;當你需要 Vector 的功能,卻又不需要它的同步特性(synchronization),則使用 ArrayList;只有在你既需要 Vector 的功能又需要它固有的同步特性時,才使用 Vector

16.若你無法確定需要保存的元素數量,可以考慮創建一個足以保存最大量資料的 array。這可能會浪費許多記憶體,但效率上的收益可能會超過記憶體方面的代價,端視你的效率評測和對系統的分析來選擇

17.盡可能復用(reuse)既有物件,而不重新創建一個新的物件。若你呼叫的函式保存的是 object reference,就不能使用復用。而如果函式在儲存動作之前先針對 object reference 創建了物件複件(copy)或克隆件(clone),你就可以復用這個物件




五. 多緒

1.面對 instance 函式,同步化(synchronized)鎖定的是物件,而非函式(method)或程式碼。記住,每個物件只有一個 lock 與之相關聯

2.當 synchronized 用於 instance 函式時,它所取得的 lock 隸屬於其呼叫者(某物件)。若 synchronized 用於 object reference,則取得的 lock 將被交給該 reference 所指的物件

3.當你叫用一個 synchronized static 函式時,獲得的 lock 將與「定義該函式」之 class 的 Class 物件相關聯,而不是與喚起函式的那個物件相關聯;當你對一個 class literal 叫用 synchronized 區段時,獲得的也是同樣那個 lock,也就是「與特定 Class 物件相關聯」的 lock

4.對於「在 synchronized 函式中可被修改的資料」,應以「private 資料 + 相應的存取函式(accessor)」取代「public/protected 資料」。若存取函式回傳的是可變物件(mutable object),那麼應該先 cloned 該物件

5.取用共享變數時,應使用 synchronized 或 volatile。如果並行性(concurrency)很重要,而且你並不需要更新很多變數,應使用 volatile;如果你需要更新許多變數,而且不願為每次取用都付出「一致化」的代價,或是你想消除並行性,則使用 synchronized

6.一旦變數被宣告為 volatile ,每次取用它們時,它們就會與主記憶體進行一致化,但如果使用 synchronized,只有在取得 lock 和釋放 lock 的時候,才會對變數和主記憶體進行一致化

7.在單一操作(single operation)中鎖定所有用到的物件

8.以固定而全域性的順序取得多個 locks 以避免因多個執行緒相互等待而導致 deadlock

9.優先使用 notifyAll() 而非 notify()。因 notify() 只能喚醒「一個」執行緒,而當多個執行緒處於等待狀態時,你無法預期被喚醒的是哪一個執行緒。千萬不要對「執行緒被喚醒的順序」、「執行緒如何被排程」有任何假設,因為會因平台不同而不同

10.針對 wait() 和 notifyAll() 使用旋鎖(spin locks),以除掉某些潛在 bug

11.使用 wait() 和 notifyAll() 取代輪詢迴圈(polling loops)

12.不要對 locked object(上鎖物件) 之 object reference 重新賦值

13.不要呼叫 stop() 或 suspend()。當 stop() 中止一個執行緒時,會釋放執行緒的所有 locks,導致搗亂內部資料的風險;而suspend() 則會帶來 deadlock 的風險




六. 類別與介面

1.要支援多重繼承(multiple inheritance),可運用介面(interfaces)

2.避免兩個 interfaces 對常數使用相同名稱,或以相同名稱或署名式(signature)來宣告函式。如果一個 class 同時實現兩個或多個具有此特徵的 interfaces,則會引發衝突

3.如只需提供部分實作(partial implementation),則使用 abstract classes

4.欲傳送或接受 mutable objects(可變物件)之 object reference 時,則實施 clone(),否則你的物件不變性(immutability)就得不到保證

5.使用繼承(inheritance)或委託(delegation)來定義 immutable object(不可變類別)

6.實作 clone() 時記得呼叫 super.clone(),以確保 java.lang.Object 的 clone() 最終會被呼叫,使克隆件得以被正確建構出來

7.我們無法保證 finalize() 是否被呼叫、何時被呼叫,所以你應提供一個 public 專責函式,用以執行 non-memory(記憶體以外)資源的清理工作;該函式英油 class 的 finalize() 呼叫之

沒有留言:

張貼留言