from:http://blog.csdn.net/ilibaba/archive/2009/05/21/4207511.aspx
NO. 26 謹慎使用重載
一個常見的問題是:
public static String classify(Set s)
{ return “set”;}
public static String classify(List l)
{return “list”;}
public static String classify(Collection c)
{return “unknown collection”;}
public static void main(String[] args)
{ Collection[] tests=new Collection[]{
new HashSet(), new ArrayList(), new HashMap.values()};
for(int i=0;i<tests.length;i++){System.out.println(classify(test[i]));}
程式結果是print出三個unknown collection。因為classify被重載了,到底調用哪個重載函數叔編譯時間刻決定的,因為編譯時間,大家的參數類型都是Collection。
避免方法重載機制的混淆。永遠不要匯出兩個具有相同參數數目的重載方法,是一個安全而保守的策略。
至少應該保證,當傳遞同樣的參數時,所有的重載方法行為一致。
NO.27 返回零長度的數組而不是null
如果返回null,對於每次調用到該方法的時候都需要做null判斷,否則很容易拋出null 指標異常,推薦返回一個零長度的數組,在通常情況下,這樣的做法對效能幾乎沒有影響。
NO.28 為所有匯出的API元素編寫文檔注釋
需要增加註釋的地方:類、介面、建構函式、方法和域聲明,
方法注釋的內容:
調用該方法的前提條件;
調用後的後續處理(如捕獲異常);
副作用(如方法啟動線程後帶來的安全性);
參數@param Describe;
返回@return Describe;
異常@throws if.....;
注意:注釋中可以適用<p><code><tt>等HTML標籤,但>,<等標籤需要轉義。
NO.29 講局部變數的範圍最小化
在第一次適用局部變數的地方聲明他;(不要過早地聲明它)
要防止局部變數在“使用它的塊”之外聲明,這樣會防止局部變數被意外使用;
幾乎每一個局部變數的需要初始化,如果沒有足夠的資訊來對一個變數進行有意義的初始化,那就延遲這個聲明,直到可以初始化為止;
其他的方法,例如,把一個變數多的方法分成兩個,每次操作一個方法,減少變數之間的幹擾。
NO.30 瞭解和使用庫
不要從頭髮明輪子,如果你要做的事情是很常見的,就去查下有沒有這樣的實作類別,如果有,則使用它,這樣會降低你實現相應功能的投入和代碼的出錯率。
NO.31 如果要求精確的答案,請避免使用 float和 double
float和double不適合表示貨幣,在平時的使用中應該避免。
例如:System.out.println(1.00-9*.10); //會輸出0.09999999999999998
如果希望系統來處理十進位的小數點,可以使用 BigDecimal。如果不考慮小數的處理,數值範圍沒有超過9位的則可以用int來處理,如果不超過18位的,則可以用long來處理,超過18位的就必須用 BigDecimal處理。
NO.32 如果其他類型更適合,則盡量避免使用字串
如果可以使用更加合適的資料類型,或者可以編寫更加恰當的資料類型(如 DO、 POJO、枚舉等),那麼應該避免使用字串來表示對象,若使用不當,字串比其他類型更加笨拙,缺乏靈活性。
NO.33 瞭解字串串連的效能
為串連 N個字串而重複得地使用“ +”串連,要消耗N的平方層級的時間。為了獲得更高的效能,請使用 StringBuffer代替 String。
NO.34 通過介面引用對象
應該優先使用介面而不是類來引用對象,如果有合適的介面存在,那麼對參數的傳回值、變數和域的聲明都應該使用介面類型,如 Vector是List介面的實現,在聲明時應該如下:
List subscribers = new Vector();
//而不是
Vector subscribers = new Vector();
例外的情況:
① 當沒有合適的介面存在,可以用類而不是介面來引用一個對象,如: String、 Integer;
② 當一個對象是基本類型的類,而不是介面時,應該用相關的基類引用這個對象,如: java.util.TimerTask;
③ 當一個類實現了一個介面,但它提供了介面中不存在的額外方法,如果程式依賴於這些額外的方法,那麼這樣的類應該只被用來引用它的執行個體,永遠不應該被用作參數類型。
NO.35 介面優先於映像機制(反射機制)
反射機制是 Java一項強大的功能,給定一個class執行個體,你可以獲得constructor、method和field執行個體。對於一些特定複雜的程式設計中非常必要(如現在很流行的 spring架構),但在並非必須使用反射機制時,盡量避免使用反射,原因如下:
① 它在編譯時間不會進行類型檢查;
② 實現代碼冗長乏味,不易閱讀;
③ 效能與一般的方法調用相比,要低下很多;
如果一個程式必須要與編譯時間未知的類一起工作,那麼最好是用反射執行個體化對象,而訪問對象時使用編譯時間刻已知的某個介面或者父類。
NO.36 謹慎地使用本地方法
盡量使用 Java自身提供的方法來代替本地方法(如用 Java提供的新功能來代替以前只有 C語言能實現個的功能),這樣可以使系統變得更加安全,系統可移植性更高,也使代碼變得更加容易閱讀,如果一定要使用本地方法,請加強測試,並儘可能的少用。
NO.37 謹慎地進行最佳化
努力寫好的程式而不是快的程式,程式要體現資訊隱藏的原則,效能問題應該是設計階段就考慮,要避免那些限制效能的設計決定,如:應該用複合模式的公有類使用繼承,則該類的效能永久的受其父類效能的影響,為了獲得好的效能而對 API進行修改並非是一個好的做法,通常,這些做法對效能並沒什麼多大的影響,如果一個系統有了清晰、簡明、結構良好的實現,請謹慎對其進行最佳化,因為 80%的效能問題存在於 20%的代碼中,找出影響效能的代碼才是問題的關鍵,可以藉助一些效能分析的工具。
第38條:遵守普遍接受的命名慣例
java的命名慣例分為兩大類:字面的和文法的。 字面命名慣例涉及包、類、介面、方法和域。 包的名字是階層的,用句號分隔第一部分。每一部分的長度不要超過8,由小寫字母和數字組成(數字少見用),鼓勵使用有意義的縮寫。除了java和javax外,一般以網域名稱做開頭,順序是頂級網域名稱放在最前面。 類和介面的名字應至少1至多個單詞,每個單詞的首字母大寫(駝峰試),盡量避免縮寫。
方法和域的名字與類和介面的名字遵守相同的字面慣例,只是第一個首字母要小寫。常量域要全部字母都大寫,詞之間通過底線區分。
文法命名慣例比字面慣例更靈活。
- 類通常用一個名詞或名詞短語,介面或者與類相同,或者以"-able"或"-ible"結尾的形容詞。
- 執行某個動作的方法,常用一個動詞或動詞短語,
- 對於返回boolean類型的方法,名字常以“is"開頭後加一個名詞或形容詞或短語,
- 如果返回的不是boolean,則常用一個名詞/短語,或以"get"開頭的動詞短語。
- 如果一方法所在的類是一個Bean,則強制要求以get開頭。
- 如果類包含對屬性操作,常用setAttribute或getAttribute格式命名。
轉換物件類型的方法,
- 如果返回不同類型的獨立的對象,則稱為toType
- 如果返回一個視圖,則用asType,
- 如果返回與被調用對象同值的原語類型,稱為typeValue
- 靜態工廠的方法,常用valueOf或getInstance.
NO.39 只針對不正常的條件才使用異常
異常只應該被用於不正常的條件,它們永遠不應被用於正常的控制流程。
下面是一個用異常作遍曆結束條件的濫用異常的例子:
- //horrible abuse of exceptions. Don't ever do this!
- try{
- int i=0;
- while(true)a[i++].f();
- }catch(ArrayIndexOutOfBoundsException e){
- ...
- }
其錯有三:
1、建立、拋出和捕獲異常的開銷是很昂貴的。因為它的初衷是用於不正常的情形,少有jvm會它進行效能最佳化。
2、把代碼放在try-catch中會阻止jvm實現本來可能要執行的某些特定的最佳化。
3、有些現代的jvm對迴圈進行最佳化,不會出現冗餘的檢查。
完全可以使用標準的實現方式:
- for(int i=0;i<a.length;i++){
- a[i].f();
- }
這條原則也適用於API設計。一個設計良好的API不應該強迫它的客戶為了正常的控制流程而使用異常。如果類中有一個”狀態相關”的方法,即只有 特定的條件下可被調用的方法,則這個類也應有一個單獨的“狀態測試”方法,以為調用這個狀態相關方法前的檢查。如Collection類的next方法和 hasNext方法。
- for(Iterator i=collection.iterator();i.hasNext();){
- Foo foo=(Foo)i.next();
- ...
- }
第40條:對於可恢複的條件使用被檢查的異常,對於程式錯誤使用運行時異常
java提供了三種可拋出的異常:被檢查的異常(checked Exception)、運行時異常(run-time Exception)和錯誤(error)。 如果期望調用者在調用時出現的異常能夠恢複,則應該使用被檢查的異常,通過拋出一個被檢查的異常,強迫調用者在catch中處理該異常,或者將異常傳播到外面。
對於一個方法聲明要拋出的每一個被檢查的異常,它是對API使用者的一種潛在指示:與異常相關聯的條件是調用這次個方法的一種可能結果。
兩種未被檢查的可拋出結構:運行時異常和錯誤,在行為上相同的,它們都不需要、也不應該被捕獲的拋出物。你所實現的所有未被檢查的拋出結構都應是 RuntimeException的子類。定義一個非Exception、RuntimeException或Error子類的拋出物是可行的,但從行為 意義上它等同於普通的被檢查異常(即Exception子類而非RuntimeException子類).
異常是個完全意義上的對象,在其上可以定義任意的方法。因被檢查的異常往往指示了可恢複的條件,所以可通過定義方法,使調用者可獲得一些有助於恢複的資訊。具體可參見“Java異常的分類”http://blog.csdn.net/ilibaba/archive/2009/03/07/3965359.aspx
NO.41 避免不必要地使用被檢查的異常
與傳回碼不同,被檢查的異常強迫程式處理例外的情況,從而大大地提高了程式的可靠性。而過分地使用被檢查的異常,則增加了不可忽視的負擔。如果正 確地使用API並不能阻止這種異常條件的產生,並且一旦產生了異常,使用API的程式可以採取有用的動作,那麼這種負擔被認為是正當的。
- try{
- ...
- }catch(TheCheckedException e){
- e.printStackTree();
- System.exit(1);
- }
如果使用API的程式員無法做得比這更好,那麼未被檢查的異常可能更為合適。在實踐中,catch幾乎總有宣告失敗的特徵。
“把被檢查的異常變成未被檢查的異常”的一種技術是,把這個要拋出異常的方法分成兩個方法,第一個方法返回一個boolean以指明是否要拋出異常,另一個執行真正的功能,如果條件不滿足就拋異常。如下:
//Invocation with checked exception
- try{
- obj.action(args);
- }catch(TheCheckedException e){
- //Handle exception condition
- }
轉換為:
- //Invocation with state-testing method and unchecked exception
- if(obj.actionPermitted(args)){
- obj.action(args));
- }else{
- //handle exception condition
- }
當然這種轉換並不總是合適的,例如一對象將在缺少外部同步的情況下被並發訪問,或者可被外界改變狀態,那麼這種轉換將是不合適的。
NO.42 盡量使用標準的異常
Java平台庫中訖今為止最常被重用的異常如下:
IllegalArgumentException 參數值不合適
IllegalStateException 對於這個方法調用而言,對象的狀態不合適(如初始化不恰當)
NullPointerException 在null被禁止的情況下參數值為null
IndexOutOfBoundsException 下標越界
ConcurrentModificationException 在禁止並發修改的情況下,對象檢測到並發修改
UnsupportedOperationException 對象不支援客戶請求的方法
其它的異常也可以使用,只要確保拋出異常的條件與文檔要求一致即可。
NO.43 拋出的異常要適合於相應的抽象
高層的實現,應該捕獲低層的異常,同時拋出一個可以按照高層抽象進行解釋的異常,這種做法叫做異常轉譯(exception translation)。即如:
- //exception translation!
- try{
- //use lowlevel abstraction to do our bidding
- ...
- }catch(LowerLevelException e){
- throw new HigherLevelException(...);
- }
低層的異常被高層的異常儲存起來,且高層的異常提供一個公有的存取方法來獲得低層的異常,這種做叫做異常連結(exception chaining)。
- //Exception chaining.
- try{
- //use lower-level abstraction to do our bindding
- ...
- }catch(LowerLevelException e){
- throw new HigherLevelException(e);
- }
異常鏈的實現非常簡單,在1.4及以後版本中,可以通過Throwable來獲得支援。
- //Exception chaining in release 1.4 or later
- HigherLevelException(Throwable t){
- super(t);
- }
處理來自低層的異常,
最好的做法是,在調用低層方法之前通過一些檢查等手段來確保它們會成功執行;
其次的做法是,讓高層處理這些異常,從而將高層方法的調用者與低層的問題隔離開;
一般的做法是使用異常轉譯;
如果低層方法的異常對高層也是合適的,則將其從低層傳到高層。
NO.44 每個方法拋出的異常都要有文檔
總是要單獨地聲明被檢查的異常,並且利用javadoc的@throws標記,準確地記錄下每個異常被拋出的條件。
使用javadoc的@throws標籤積累下一個方法可能會拋出的每個未被檢查的異常,但是不要使用throws關鍵字將未被檢查的異常包含在方法的聲明中。
如果一個類中的許多方法出於同樣的原因而拋出同一個異常,那麼在該類的文檔注釋中對這個異常做文檔,而不是為每個方法單獨寫一個文檔。
NO.45 在細節訊息中包含失敗 - 捕獲資訊
為了捕獲失敗,異常資訊應該儘可能多的包含有意義的參數和域的值,如:IndexOutOfBoundsException應該包括上界、下界以及實際的下標。
NO.46 努力使失敗保持原子性
一個失敗的方法調用應該使對象保持“它在被調用之前的狀態”,方法如下:
① 使用非可變對象;
② 在可變對象上的操作,應在執行前檢查參數的有效性;
③ 寫一段恢複代碼,在執行失敗的情況下,復原到操作開始之前的狀態(不常用);
④ 在對象的一份臨時拷貝上執行操作,當完成之後,再把臨時拷貝中的結果複製給原來的對象,如Collections.sort在執行排序之前,先把它的輸入轉儲到一個數組中,這樣既能降低內迴圈的開銷,又能保證在排序失敗的時候,輸入列表將保持原樣。
NO.47 不要忽略異常
除非有特殊的需求(如動畫中的多幀映像播放),否則不要吃掉異常,至少在catch塊中包含一條說明,用來解釋為什麼忽略這個異常。簡單地將一個未被檢查的異常傳播到外界至少會使程式迅速地失敗,從而保留了有助於調試該失敗條件資訊,比異常被忽略後的一個不可預測的時刻程式失敗這種情況要強。