高效使用異常指南
Item 57: Use exceptions only for exceptional conditions
只有在異常條件下才使用異常
考慮如下的代碼
//bad !! don't do this
try {
int i = 0;
while(true)
range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e){}
使用異常條件來終止遍歷操作是非常錯誤的做法
使用如下代碼代替
for (Mountain m : range)
m.climb();
名副其實,異常只能用在異常條件下,而不能用于流程控制
Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
對可恢復(fù)條件使用檢查異常蹬昌,對程序錯誤使用運行時異常犀盟。
Java提供了三種異常:checked exceptions,runtime exceptions and errors.大多數(shù)程序員對何時使用何種異常有困惑,有如下幾種原則作參考
決定使用檢查異常或非檢查異常的原則是:調(diào)用方可以合理的修復(fù)異常,現(xiàn)如今的IDE工具如eclipse或Android studio會自動提示此類異常,自動填充try catch
非檢查異常有兩種:runtime exceptions and errors
用運行時異常(runtime exceptions)識程序錯誤,絕大多數(shù)的運行時異常都表明違反了某種前提條件,如ArrayIn?dexOutOfBoundsException數(shù)組越界
有一個普遍的約定是error用于JVM,所以所有的非檢查異常都應(yīng)該繼承自 RuntimeException
常見的非檢查異常runtime exception
- NullPointerException, 空指針異常
- ArithmeticException, 算術(shù)異常
- ClassCastException, 類型強(qiáng)制轉(zhuǎn)換異常
- IllegalArgumentException, 傳遞非法參數(shù)異常
Item 59: Avoid unnecessary use of checked exceptions
避免過度使用檢查異常
如果一個方法有多個檢查異常,調(diào)用者會包裹多個catch來處理異常,這里沒有一個絕對的準(zhǔn)則,可以使用if語句把條件先過濾一遍而避免拋異常
Item 60: Favor the use of standard exceptions
使用常用異常
當(dāng)需要拋出一個異常時,盡量使用Java平臺提供好的異常,因為大家都知道什么意思
Item 61: Throw exceptions appropriate to the abstraction
拋出更合適的抽象異常
當(dāng)拋出和任務(wù)不相關(guān)的異常時容易讓人困惑,當(dāng)拋出一個低級的異常時,這種情況經(jīng)常發(fā)生,它不僅僅令人困惑,也污染了上層的調(diào)用
為了避免這個問題寒砖,上層應(yīng)該捕獲較低級別的異常谅猾,并在它們的位置上拋出可以用更高級別的抽象來解釋的異常,如:
// Exception Translation
try {
// Use lower-level abstraction to do our bidding
...
} catch(LowerLevelException e) {
throw new HigherLevelException(...);
}
更具體的例子在List<E>的方法中
/**
* Returns the element at the specified position in this list.
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()}).
*/
public E get(int index) {
ListIterator<E> i = listIterator(index);
try {
return i.next();
} catch(NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
這種特殊的異常處理方式被稱為"異常鏈",但是也不應(yīng)該過度使用
我們也可以在在上層調(diào)用中拋出底層錯誤的原因
// Exception Chaining
try {
... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
最好的方式還是盡量在底層避免異常,如果不能處理在考慮"異常鏈"的方式
Item 62: Document all exceptions thrown by each method
為每個方法拋出的異常添加文檔注釋
對于檢查異常,要說明前置條件,并用@throws標(biāo)記合適的異常,不要為了圖簡單直接 @throws Exception
如果一個異常被多個方法拋出,并且是相同的原因,則不要在方法上注釋,而是要在類上添加異常注釋
Item 63: Include failure-capture information in detail messages
打印出異常的詳細(xì)信息以便于分析
Item 64: Strive for failure atomicity
保證錯誤的原子性(調(diào)用一千次,輸出一樣)
即使一個異常發(fā)生了,我們也希望對象能正常使用
有幾種方式可以達(dá)到這一點,最簡單的就是創(chuàng)建不可變的對象,如果一個對象是不可變的,原子性也隨之而來
對于可變對象,常見的方式是檢查參數(shù)的合法性(Item 38)
第三種方式是發(fā)生異常后,回滾異常狀態(tài)為使用前的初始狀態(tài)
最后一種方式使用拷貝來避免發(fā)生錯誤時改變原來對象的狀態(tài),如Collections.sort,排序方法會先轉(zhuǎn)成array數(shù)組來排序,本來是為了提高性能,額外的如果發(fā)生了錯誤不會改變原集合的狀態(tài)
雖然原子性能保證,但是實際使用中并不總是能達(dá)到滿意的狀態(tài),如兩個線程同時修改一個對象,沒有同步的情況下,引發(fā)了currentModificationException.這時如果恢復(fù)對象的狀態(tài)依然不能使程序正確運行
作為一種規(guī)則來講,當(dāng)發(fā)生異常時總能確保當(dāng)前對象的狀態(tài),可惜目前很多API都沒有遵守
Item 65: Don’t ignore exceptions
不要忽略異常
這條建議看似很明顯,但是值得重復(fù),當(dāng)API的設(shè)計者聲明了一種異常,他們是為了告訴你什么,不要忽略它!很容易包裹一個空的try catch語句就不管這個異常了.如:
try {
...
} catch (SomeException e) {
}
空處理并不是拋異常的初衷,目的是為了強(qiáng)制解決這個異常條件.忽略異常就像忽略火警廣播,有人關(guān)了廣播導(dǎo)致其他人并不知道發(fā)生了火災(zāi).如果確實需要一個空的處理,需要詳細(xì)說明為何空處理是合適的