第57條:只針對(duì)異常的情況處理異常
try{
int i = 0;
while(true)
range[i++].climb();
}catch(ArrayIndexOutOfBoundsException e){
}
用拋出(throw)、捕獲(catch)亏较、忽略ArrayIndexOutOfBoundsException的手段來(lái)達(dá)到終止無(wú)限循環(huán)的目的雪情。這是極其不合適的。原因有:
1.異常機(jī)制的設(shè)計(jì)初衷是用于不正常的情形尘执,所以很少會(huì)有JVM實(shí)現(xiàn)試圖對(duì)它們進(jìn)行優(yōu)化宴凉,使得與顯式的測(cè)試一樣快速。
2.把代碼放在try-catch塊中反而阻止了現(xiàn)代JVM實(shí)現(xiàn)本來(lái)可能要執(zhí)行的某些特定優(yōu)化丧靡。
3.對(duì)數(shù)組進(jìn)行遍歷的標(biāo)準(zhǔn)模式并不會(huì)導(dǎo)致冗余的檢查籽暇。有些現(xiàn)代的JVM實(shí)現(xiàn)會(huì)將它們優(yōu)化掉。
基于異常的循環(huán)模式不僅模糊了代碼的意圖熬荆,降低了它的性能绸狐,而且它還不能保證正常的工作!可能掩蓋Bug(捕捉到了異常突琳,但不做任何處理)!
這條原則對(duì)于API設(shè)計(jì)也有啟發(fā)拆座。設(shè)計(jì)良好的API不應(yīng)該強(qiáng)迫它的客戶(hù)端為了正常的控制流而使用異常冠息。
1.如果類(lèi)具有“狀態(tài)相關(guān)”的方法逛艰,往往也應(yīng)該有“狀態(tài)測(cè)試”的方法。比如散怖,Iterator接口有一個(gè)“狀態(tài)相關(guān)”的next方法镇眷,和相應(yīng)的狀態(tài)測(cè)試方法hasNext。所以對(duì)集合進(jìn)行迭代的標(biāo)準(zhǔn)模式:
for(Iterator<Foo> i = collection.iterator(); i.hasNext();){
Foo foo = i.next();
}
而不是通過(guò)異常捕獲的方式結(jié)束遍歷永乌。
2.如果狀態(tài)相關(guān)的方法被調(diào)用時(shí)具伍,該對(duì)象處于不適當(dāng)?shù)臓顟B(tài)之中,它就會(huì)返回一個(gè)可識(shí)別的值望几,比如null萤厅。但這個(gè)并不適合Iterator,因?yàn)閚ull也是next的合法返回值害碾。
第58條:對(duì)可恢復(fù)的情況使用受檢異常赦拘,對(duì)編程錯(cuò)誤使用運(yùn)行時(shí)異常
Java程序設(shè)計(jì)提供三種可拋出的異常躺同,受檢異常、運(yùn)行時(shí)異常和錯(cuò)誤剃袍。(運(yùn)行時(shí)異常和錯(cuò)誤又稱(chēng)為非受檢異常)捎谨。
受檢異常應(yīng)該被捕獲,非受檢異常不應(yīng)該被捕獲畏邢。
第59條:避免不必要地使用受檢的異常
受檢異常強(qiáng)迫程序員處理異常的條件检吆,大大增強(qiáng)了可靠性。過(guò)分使用受檢的異常會(huì)使API使用起來(lái)非常不方便臂寝。
如果正確地使用API并不能阻止這種異常條件的產(chǎn)生摊灭,
并且一旦產(chǎn)生異常帚呼,使用API的程序員可以立即采取有用的動(dòng)作(恢復(fù)),
除非上述兩個(gè)條件都成立萝挤,否則更適合使用未受檢的異常怜珍。
把受檢異常變?yōu)槲词軝z異常:
把拋出異常的方法分成兩個(gè)方法,其中第一個(gè)方法返回一個(gè)boolean今豆,表明是否應(yīng)該拋出異常柔袁。
try{
obj.action(args);
}catch(TheCheckedException e){
//Handle exceptional condition
...
}
重構(gòu)為
if(obj.actionPermitted(args)){
obj.action(args);
}else{
//Handle exception condition
...
}
如果明知道會(huì)調(diào)用成功,或者不介意由于調(diào)用失敗而導(dǎo)致的線(xiàn)程終止插掂,可以使用:
obj.action(args);
第60條:優(yōu)先使用標(biāo)準(zhǔn)的異常
常見(jiàn)的異常
異常 | 使用場(chǎng)合 |
---|---|
NullPointerException | 在禁止使用null的情況下,對(duì)象為null |
IndexOutOfBoundsException | 下表參數(shù)值越界 |
IllegalArgumentException | 非null的參數(shù)值不正確 |
IllegalStateException | 對(duì)于方法調(diào)用而言酝润,對(duì)象狀態(tài)不合適 |
第61條:拋出與抽象相對(duì)應(yīng)的異常
當(dāng)方法傳遞 由低層抽象拋出的異常時(shí)要销,往往會(huì)出現(xiàn)方法拋出的異常與它所執(zhí)行的任務(wù)沒(méi)有明顯的聯(lián)系夏块。
因此,高層的實(shí)現(xiàn)應(yīng)該捕獲低層的異常浑塞,同時(shí)拋出可以按照高層抽象進(jìn)行解釋的異常患民。這叫“異常轉(zhuǎn)譯”。
try{
//Use lower-level abstraction to do our bidding
...
}catch(LowerLevelException e){
throw new HigherLevelException(...);
}
例如AbstractSequentialList類(lèi):
/**
* Returns the element at the specified position in this list.
*
* <p>This implementation first gets a list iterator pointing to the
* indexed element (with <tt>listIterator(index)</tt>). Then, it gets
* the element using <tt>ListIterator.next</tt> and returns it.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
第62條:每個(gè)方法拋出的異常都要有文檔
描述一個(gè)方法所拋出的異常,是正確使用這個(gè)方法時(shí)所需要文檔的重要組成部分印蓖。
始終要單獨(dú)地聲明受檢的異常,并且利用Javadoc的@throws標(biāo)記赦肃,準(zhǔn)確地記錄下拋出每個(gè)異常的條件。明確的聲明所有拋出的異常船侧。
對(duì)于可能拋出的非受檢異常厅各,也要為它們建立文檔队塘,這樣可以有效地描述出這個(gè)方法被成功執(zhí)行的前提條件。
//ConcurrentHashMap的get方法憔古,key不能為空值鸿市。
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
* ......
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
......
}
在接口中的方法即碗,在文檔中記錄下它可能拋出的未受檢異常顯得尤為重要涝桅。這份文檔成了該接口的通用約定冯遂。比如ConcurrentMap接口的putIfAbsent()方法可能拋出NullPointerException谒获,文檔中約定了任何繼承了ConcurrentMap的類(lèi)的key和value都不能為null。
public interface ConcurrentMap<K, V> extends Map<K, V> {
/**
* If the specified key is not already associated
* with a value, associate it with the given value.
* This is equivalent to
* <pre>
* if (!map.containsKey(key))
* return map.put(key, value);
* else
* return map.get(key);</pre>
* except that the action is performed atomically.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key, or
* <tt>null</tt> if there was no mapping for the key.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with the key,
* if the implementation supports null values.)
* @throws UnsupportedOperationException if the <tt>put</tt> operation
* is not supported by this map
* @throws ClassCastException if the class of the specified key or value
* prevents it from being stored in this map
* @throws NullPointerException if the specified key or value is null,
* and this map does not permit null keys or values
* @throws IllegalArgumentException if some property of the specified key
* or value prevents it from being stored in this map
*
*/
V putIfAbsent(K key, V value);
......
}
使用Javadoc的@throws標(biāo)簽記錄下一個(gè)方法可能拋出的每個(gè)未受檢異常,但是不要使用throws關(guān)鍵字將未受檢的異常包含在方法的聲明中炒俱。
throws關(guān)鍵字只將受檢的異常包含在方法的聲明中爪膊。這樣有Javadoc的@throws標(biāo)簽所產(chǎn)生的文檔就會(huì)有明顯的提示信息來(lái)幫助區(qū)分受檢異常和非受檢異常。
總結(jié):使用Javadoc的@throws標(biāo)簽將方法可能拋出的所有異常都要明確地記錄下來(lái)峦阁,而在方法聲明的throws關(guān)鍵字耘成,只記錄受檢異常瘪菌。這樣程序員就可以明確地區(qū)分方法所拋出來(lái)的受檢異常和非受檢異常。
第63條:在細(xì)節(jié)消息中包含能捕獲失敗的信息
為了捕獲失敗诵肛,異常的細(xì)節(jié)信息應(yīng)該包含所有“對(duì)異常有貢獻(xiàn)”的參數(shù)和域的值疆栏。
第64條:努力使失敗保持原子性
一般而言,失敗的方法調(diào)用應(yīng)該使對(duì)象保持在被調(diào)用之前的狀態(tài)珠洗。具有這種屬性的方法被稱(chēng)為具有失敗原子性若专。
有幾種途徑可以實(shí)現(xiàn)這種效果:
1.設(shè)計(jì)一個(gè)不可變對(duì)象;
2.在執(zhí)行操作之前檢查參數(shù)的有效性膊爪;
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elememts[--size];
elements[size] = null; //銷(xiāo)除廢棄的引用
return result;
}
如果不對(duì)size進(jìn)行檢查米酬,這個(gè)方法仍會(huì)拋出異常,但是加派,size的值保持在不一致的狀態(tài)(-1)之中跳芳。
3.寫(xiě)一段恢復(fù)代碼,回滾
4.在對(duì)象的一份臨時(shí)拷貝上執(zhí)行操作娄琉,當(dāng)操作完成之后再用臨時(shí)拷貝中的結(jié)果代替對(duì)象的內(nèi)容吓歇。比如照瘾,Collections.sort在執(zhí)行排序之前,首先把輸入列表轉(zhuǎn)到了一個(gè)數(shù)組中主卫。
第65條:不要忽略異常
try{
...
}catch(SomeException e){
}
空的catch塊會(huì)使異常達(dá)不到應(yīng)有的目的鹃愤。