1. 程式人生 > >《Effective Java》第8章 通用程式設計

《Effective Java》第8章 通用程式設計

本章主要涉及Java語言的具體細節,討論了局部變數的處理,控制結構,類庫的用法,各種資料型別的用法,以及兩種不是語言本身提供的機制(reflection和native method,虛擬機器JVM提供支援的)的用法。

1.將區域性變數的作用域最小化 【Item 45】
1) Java允許你在任何可以出現語句的地方宣告變數
2) 要使區域性變數的作用域最小化,最有力的方法就是在第一次使用它的地方宣告
3) 幾乎每個區域性變數的宣告都應該包含一個初始化表示式,還不能初始化時就儘量推遲宣告到可以初始化的位置
4) 無論是傳統的還是for-each形式的for迴圈,都允許宣告迴圈變數,它們的作用域被限定在正好需要的範圍之內,因此在迴圈終止之後不再需要迴圈變數的內容,for迴圈就優先於while迴圈,避免無意識錯誤的發生
2.for-each迴圈優先於傳統的for迴圈 【Item 46】


1) java 1.5引入的for-each迴圈,通過完全隱藏迭代器或者索引變數,避免了混亂和出錯的可能,這種模式適用於實現過Iterable介面的類,如陣列和集合
2) 那麼為了可以使用for-each迴圈,你在編寫的型別表示一組元素時,即使你選擇不讓它實現Collection,也要讓它實現Iterable介面
3) 想要在遍歷的時候修改所遍歷的陣列或者集合無法使用for-each迴圈,這種情形有如下三種:
a. 過濾,遍歷過程中需要刪除相關元素,需要用顯示的迭代器進行,以便可以呼叫remove方法
b. 轉換,遍歷過程中想要取代其部分或者全部元素,需要用迭代器或陣列索引,以便設定元素的值
c. 平行迭代,需要並行遍歷多個集合,需要顯示的控制迭代器或者索引變數,以便所有迭代器或者索引變數都可以同步的前移
如上三種情形,可以用傳統for迴圈進行處理。
3.瞭解和使用類庫 【Item 47】

1) 通過使用標準類庫的好處:
a. 可以充分利用這些編寫標準類庫的專家的知識,以及在你之前的其他人的使用經驗
b. 不必浪費時間為那些與工作不太相關的問題提供特別的解決方案
c. 類庫的效能往往會隨著時間的推移而不斷提高,無需你做任何努力
d. 可以使自己的程式碼融入主流,這樣的程式碼更易讀,更易維護,更易被大多數開發人員重用
2) 每個程式設計師都應該熟悉java.lang,java.util,某種程度上還有java.io中的內容
3) java.util的重點Collection Framework以及java.util.concurrent關於多執行緒相關部分
4) 不建議重新發明輪子,君子要善假於物也
4.如果需要精確的答案,請避免使用float和double 【Item 48】

1) float和double尤其不適合於貨幣運算,因為在JVM實現中只是針對float和double有一些value set,在處理這類資料時經常會進行約等於的操作
2) 而精確的計算應該用BigDecimal,int或long進行貨幣計算,如果數值範圍沒有超過9位十進位制數字,就可以使用int;如果不超過18位數字,就可以使用long;如果數值可能超過18位數字,就必須使用BigDecimal
5.基本型別優先於裝箱節本型別 【Item 49】
1) 基本型別和裝箱型別之間三個區別:
a. 基本型別只有值,而裝箱基本型別則具有與它們的值不同的同一性,也就是說兩個裝箱基本型別可以具有相同的值和不同的同一性
b.基本型別只有功能完備的值,而每個裝箱基本型別除了它對應的基本型別的所有功能值之外,還有個分功能值:null
c. 基本型別通常比裝箱節本型別更加節省時間和空間
2) 對於裝箱基本型別使用==操作幾乎總是錯誤的
3) 當在一項操作中混合使用基本型別和裝箱基本型別時,裝箱基本型別就會自動拆箱,如果null物件引用被自動拆箱,就會出現一個NullPointerException
4) 變數被反覆的裝箱和拆箱,導致明顯的效能下降
5) 裝箱基本型別的應用場景:
a. 作為集合中的元素,鍵和值,不能將基本型別放入集合中,因此必須使用裝箱基本型別
b. 在引數化型別中,必須使用裝箱基本型別作為型別引數,因為Java不允許使用基本型別
c. 進行反射的方法呼叫時,必須使用裝箱基本型別
6) 自動裝箱減少了使用裝箱基本型別的繁瑣性,但是並沒有減少它的風險,當程式使用==操作符比較兩個裝箱基本型別時,它做了同一性比較,即物件引用的一致性,並不是單純的值比較
6.如果其他型別更適合,則儘量避免使用字串 【Item 50】
1) 一些不應該使用字串的情形:
a. 字串不適合代替其它的值型別,例如不應該用字串來代替int,float或者boolean等
b. 字串不適合代替列舉型別
c. 字串不適合代替聚集型別
d. 字串也不適合代替能力表
2) 如果可以使用更加合適的資料型別,或者可以編寫更加適當的資料型別,就應該避免使用字串來表示物件
7.當心字串連線的效能 【Item 51】
1) 當連線n個字串而重複地使用字串連線操作符,需要n的平方級的時間,這是由於字串不可變而導致的不幸結果,當兩個字串被連線在一起時,它們的內容都要被拷貝
2) 為了獲得可以接受的效能,請使用StringBuilder代替String,Java1.5發行版本中增加了非同步的StringBuilder類,代替現在已經過時的StringBuffer類
3) 不要使用字串連線操作符來合併多個字串,除非效能無關緊要,應該使用StringBuilder的append方來合併或者使用字元陣列,或者每次只處理一個字串,而不是將它們組合起來
8.通過介面引用物件 【Item 52】
1) 如果有合適的介面型別存在,那麼對於引數,返回值,變數和域來說,就都應該使用介面型別進行宣告
2) 如果你養成了用介面作為型別的習慣,你的程式將會更加靈活
3) 不能用介面宣告的情況:
a. 如果沒有合適的介面存在,完全可以用類而不是介面來引用物件,典型的值類通常沒有合適的介面可用,用值類本身作為引數,變數,域或者返回型別就可以了
b. 物件屬於一個框架,而框架的節本型別是類,不是介面,如果物件屬於這種基於類的框架,就應該使用相關的基類來引用這個物件,而不是它的實現類
c. 類實現了介面,但是它提供了介面中不存在的額外方法,如果程式依賴於這些額外方法,這種類就應該只被用來引用它的例項,這種類很少被用作引數型別
4) 總之建議引用物件的型別建議範圍較廣一些,這樣增加程式的靈活性,比如使用介面,或者物件的基類
9.介面優先於反射機制 【Item 53】
1) 核心反射機制java.lang.reflect提供了“通過程式來訪問關於已裝載的類的資訊”的能力
2) 反射機制的代價:
a. 喪失了編譯時型別檢查的好處
b. 執行反射訪問所需要的程式碼非常笨拙和冗長
c. 效能損失
3) 反射功能只是在設計時被用到,通常,普通應用程式在執行時不應該以反射方式訪問物件
4) 有一些複雜的應用程式需要使用反射機制,包括類瀏覽器,物件監視器,程式碼分析工具,解釋型的內嵌式系統,RPC等
5) 如果只是以非常有限的形式使用反射機制,雖然也要付出少許代價,但是可以獲取更多的好處,比如必須用到編譯時無法獲取到的類,但是在編譯時存在適當的介面或者超類,通過它們可以引用這個類
6) 如果適當的構造器不帶引數,甚至根本不需要使用java.lang.reflect包,Class.newInstance方法就已經提供了所需功能
7) 類對於在執行時可能不存在的其他類、方法、或者域的依賴性,用反射法進行管理,這種用法是合理的
8) 應該僅僅使用放射機制來例項化物件,而訪問物件時則使用編譯時已知的介面或者超類
10.謹慎地使用本地方法 【Item 54】
1) JNI(Java Native Interface)允許Java應用程式可以呼叫本地方法(native method),所謂本地方法是指的用本地程式設計語言(比如C/C++)來編寫的特殊方法
2) 本地方法的三個用途:
a. 提供了“訪問特定於平臺的機制”的能力,比如訪問登錄檔,和檔案鎖等
b. 提供了訪問遺留程式碼庫的能力,從而訪問遺留資料
c. 本地方法可以通過本地語言,編寫應用程式中主動效能的部分,以提高系統的效能
3) 本地方法的缺點:
a. 本地方法不是安全的,使用本地方法的應用程式不能再免受記憶體毀壞錯誤的影響
b. 本地語言是與平臺相關的,使用本地方法的應用程式也不再是可以自由移植的
c. 使用本地方法的應用程式也更難除錯,在進入和退出原生代碼時,需要相關的固定開銷,效能影響,難於閱讀
4) 使用本地方法前務必三思
11. 謹慎地進行優化 【Item 55】
1) 不要去計較效率上的一些小小的得失,在97%的情況下,不成熟的優化才是一切問題的根源
2) 要努力編寫好的程式而不是快的程式
3) 好的程式體現了資訊隱藏的原則:只要有可能,它們就會把設計決策集中在單個模組中,因此,可以改變單個決策,而不會影響到系統的其他部分,避免牽一髮而動全身
4) 必須在設計的時候就考慮到效能問題,努力避免那些限制性能的設計決策
5) 在一個系統設計完成之後,最難以更改的元件是那些指定了模組之間互動關係以及模組與外界互動關係的元件
6)要考慮API設計決策的效能後果:
a. 使公有的型別成為可變的,這可能會導致大量的不必要的保護性拷貝
b. 在適合使用複合模式的公有類中使用繼承,會把這個類與其它的超類永遠的束縛在一起,從而人為的限制了子類的效能
c. 在API中使用實現型別而不是介面,會把你束縛在一個具體的實現上,即使將來出現更快的實現你也無法使用
12. 遵守普遍接受的命名慣例 【Item 56】
1) 命名的慣例分為兩大類:字面的和語法的
2) 使用者建立的包的名稱絕不能以java和javax開頭,因為標準類庫和一些可選的包的名稱會以java和javax開頭
3) 包名稱其它部分包括一個或多個描述該包的組成部分,這些短語應該儘量簡短,通常不超過8個字元,可以以單詞首字母代替一個單詞的形式進行縮短
4) 類和介面的名稱,包括列舉和註解型別的名稱,都應該包括一個或者多個單詞,每個單詞首字母大寫,應該儘量避免縮寫,除非是一些首字母縮寫和一些通用的縮寫
5) 常量是唯一推薦使用下劃線的情形
6) 型別引數名稱通常由單個字母組成,這個字母通常有一下五種情形:
a. T表示任意的型別
b. E表示集合的元素型別
c. K和V表示對映的鍵和值型別
d. X表示異常
7) 當然如果長期養成的習慣用法與上述規則不同,不必盲目遵從這些命名慣例