Guava的 Throwables 工具常常可以讓exception處理更方便。
Propagation
有時(shí)候,你會(huì)想把捕獲的exception拋到上一個(gè)try/catch塊。對(duì)于 RuntimeException 和 Error 尤為如此,它們不需要 try/catch 塊,但可能被其他的 try/catch 塊無(wú)意捕獲。
Guava 提供了一些工具來(lái)簡(jiǎn)化propagate exception。例如:
try{
someMethodThatCouldThrowAnything();
}catch(IKnowWhatToDoWithThisException e){
handle(e);
}catch(Throwable t){
Throwables.propagateIfInstanceOf(t, IOException.class);
Throwables.propagateIfInstanceOf(t, SQLException.class);
throw Throwables.propagate(t);
}
這里的每個(gè)方法都會(huì)拋出它們自己的exception,但是throw最終結(jié)果 —— 如,throw Throwables.propagate(t) —— 對(duì)編譯檢查有指示作用,提示這里肯定會(huì)拋出一個(gè)exception。
這里是Guava提供的propagation方法的簡(jiǎn)單總結(jié):
參數(shù)形式解釋
RuntimeException propagate(Throwable)如果參數(shù)throwable是 RuntimeException 或 Error 則原樣propagate,否則將它包入 RuntimeException 中拋出。保證拋出。返回值是 RuntimeException 類型,因此你可以像上面那樣寫 throw Throwables.propagate(t) ,這樣Java編譯器會(huì)明白這一行肯定會(huì)拋出一個(gè)exception。
void propagateIfInstanceOf(Throwable, Class) throws X僅當(dāng)參數(shù)throwable是 X 類型時(shí),原樣propagate。
void propagateIfPossible(Throwable)僅當(dāng)參數(shù)throwable是 RuntimeException 或 Error 類型時(shí),原樣propagate。
void propagateIfPossible(Throwable, Class) throws X僅當(dāng)參數(shù) throwable 是 RuntimeException 或 Error 或 X 類型時(shí),原樣propagate。
Throwables.propagate的用途 模仿Java 7的多重catch和重新throw
一般來(lái)說(shuō),調(diào)用者如果想要讓exception沿著調(diào)用棧傳播,他只要不寫 catch 塊就可以了。既然他不打算在exception后恢復(fù),他恐怕也不需要寫入log或者采取什么其他行動(dòng)。他可能需要進(jìn)行一些清理工作,但是無(wú)論有沒(méi)有expction都會(huì)需要,因此清理工作會(huì)放在 finally 塊中。盡管如此,會(huì)重新throw的 catch 塊有時(shí)還是有意義的:也許調(diào)用者想要在傳播exception之前先更新崩潰計(jì)數(shù)器,或者他只想在特定條件下傳播exception。
只有一種exception的時(shí)候,catch和重新throw是簡(jiǎn)單直接的。麻煩的是有多種exception的時(shí)候:
@Overridepublicvoid run(){
try{
delegate.run();
}catch(RuntimeException e){
failures.increment();
throw e;
}catch(Error e){
failures.increment();
throw e;
}
}
Java 7用 multicatch 來(lái)解決這個(gè)問(wèn)題:
}catch(RuntimeException|Error e){
failures.increment();
throw e;
}
但不用Java 7的用戶就沒(méi)辦法了。他們也想寫如下的代碼,但編譯器不允許拋出Throwable類型的變量:
}catch(Throwable t){
failures.increment();
throw t;
}
解決方案是用 throw Throwables.propagate(t) 來(lái)替換 throw t 。僅就這種情況而言, Throwables.propagate 跟之前代碼的功能完全相同。但是,代碼里寫 Throwables.propagate 很容易有一種隱藏的副作用。具體來(lái)說(shuō),要注意上面這種模式只適用于 RuntimeException 和 Error。如果 catch 塊可能捕捉checked exception,你還需要調(diào)用 propagateIfInstanceOf 來(lái)保證正常功能,因?yàn)?Throwables.propagate 無(wú)法直接傳播checked exception。
總體來(lái)說(shuō),這種 propagate 的用法效果一般。在Java 7下沒(méi)必要這樣做。在其他版本的Java下,這樣能略微減少重復(fù),但是一個(gè)簡(jiǎn)單的Extract Method重構(gòu)也可以達(dá)到同樣效果。另外,使用 propagate makes it easy to accidentally wrap checked exceptions.
無(wú)用功:把 throws Throwable 轉(zhuǎn)化為 throws Exception
某些API,尤其是Java reflection API 和 (相應(yīng)的) JUnit,有拋出 Throwable 的方法。運(yùn)用這些API可能很麻煩,因?yàn)榫退闶亲钔ㄓ玫腁PI一般也只聲明throws Exception。 Throwables.propagate 是為非 Exception ,非 Error 的 Throwable 準(zhǔn)備的。這個(gè)例子聲明了一個(gè)執(zhí)行JUnit測(cè)試的 Callable :
public void call() throws Exception{
try{
FooTest.super.runTest();
}catch(Throwable t){
Throwables.propagateIfPossible(t,Exception.class);
Throwables.propagate(t);
}
return null;
}
這里沒(méi)必要propagate(),并且第二行與="throw new RuntimeException(t)"等價(jià)。 (順便說(shuō)一句:這個(gè)例子也提醒了我 propagateIfPossible 可能令人迷惑,因?yàn)樗粌H傳播參數(shù)指定的類型,也傳播 Error 和 RuntimeException。)
這種模式 (或者類似的寫法,如"throw new RuntimeException(t)") 在Google的代碼庫(kù)里至少出現(xiàn) 30 次。(搜索'propagateIfPossible[^;]* Exception.class[)];'試試。) 采用"throw new RuntimeException(t)"寫法的略占多數(shù)。我們也有可能想要一個(gè)"throwWrappingWeirdThrowable"方法來(lái)做Throwable到Exception的轉(zhuǎn)換,但是既然兩行就能搞定,這個(gè)方法還是沒(méi)有太大必要,除非我們要廢除propagateIfPossible方法。sat答案
Throwables.propagate有爭(zhēng)議的用法 有爭(zhēng)議:把 checked exception 轉(zhuǎn)化為 unchecked exception
理論上,unchecked exception表示有bug,checked exceptions表示在你控制范圍之外的問(wèn)題。但在實(shí)踐上,即使JDK有時(shí)也會(huì)用錯(cuò) (至少,對(duì)于某些方法,沒(méi)有普遍認(rèn)同的正確答案)。
因此,有時(shí)調(diào)用者需要讓這兩種exception類型相互轉(zhuǎn)化:
try{
return Integer.parseInt(userInput);
}catch(NumberFormatException e){
throw new InvalidInputException(e);
}
try{
return publicInterfaceMethod.invoke();
}catch(IllegalAccessException e){
throw new AssertionError(e);
}
有時(shí)候,這些調(diào)用者會(huì)用 Throwables.propagate 。有什么壞處呢?最主要的問(wèn)題是代碼的含義不太明顯。throw Throwables.propagate(ioException) 有什么效果?throw new RuntimeException(ioException) 有什么效果?這兩行代碼功能是相同的,但后者更直白。前者使人生疑:"這是在做什么?應(yīng)該不只是打包成 RuntimeException 吧?如果只為這個(gè),為何不寫一個(gè)wrapper方法呢?"只能承認(rèn),部分問(wèn)題在于"propagate"這個(gè)名字很含糊。(它是一個(gè)拋出未聲明exception的方式嗎?) 也許換成"wrapIfChecked"會(huì)好一些。但即使叫這個(gè)名字,在已知checked exception上調(diào)用它也沒(méi)有什么優(yōu)勢(shì)。可能還會(huì)有副作用: 也許會(huì)有比普通 RuntimeException 更合適的拋出類型 -- 比如, IllegalArgumentException。
我們有時(shí)也會(huì)看到在exception僅僅有可能是checked exception時(shí)用 propagate 。相對(duì)來(lái)說(shuō),這種做法后果小一些,也不那么直白:
}catch(RuntimeException e){
throw e;
}catch(Exception e){
throw new RuntimeException(e);
}
}catch(Exception e){
throw Throwables.propagate(e);
}
盡管如此,這里不可忽視的問(wèn)題在于將checked exception轉(zhuǎn)化為unchecked exception的行為本身。在某些情況下是無(wú)可厚非的,但更多的時(shí)候這樣做是在逃避對(duì)常規(guī) checked exception 的處理。這讓我們思考checked exception本身是否就是一個(gè)壞主意。我不想說(shuō)得這么深入。姑且說(shuō) ,Throwables.propagate 不是讓 Java 使用者用來(lái)忽略 IOException 及類似異常的。
有爭(zhēng)議: Exception隧道托福答案
但是當(dāng)你實(shí)現(xiàn)一個(gè)不允許throw exception的方法時(shí)怎么辦呢?有時(shí)你需要把exception打包在unchecked exception中。這樣沒(méi)問(wèn)題,但同樣的,對(duì)于單純的打包 propagate 沒(méi)有必要。事實(shí)上,自己實(shí)現(xiàn)打包會(huì)更好一些:如果你把每個(gè)exception(而不只是checked exception)打包,那就可以在另一端解包,減少特殊處理。另外,最好打包成自定義的exception類型。
有爭(zhēng)議: 重新拋出其他線程的exception
try{
return future.get();
}catch(ExecutionException e){
throw Throwables.propagate(e.getCause());
}
這里有幾件要考慮的事:
對(duì)于checked exception:參見(jiàn)上面的"把 checked exception 轉(zhuǎn)化為 unchecked exception"一節(jié)。但如果此處已知不會(huì)拋出checked exception呢? (也許是 Runnable 的結(jié)果。) 如上所述,你可以catch這個(gè)exception然后拋出 AssertionError ;propagate 沒(méi)有什么用。特別的是,對(duì)于Future,也可以考慮Futures.get。
對(duì)于非Exception,非Error的Throwable。(好吧,這個(gè)可能性不大,但是如果你試圖直接重新throw它,編譯器會(huì)強(qiáng)迫你考慮這種可能) 參考上面的 "把 throws Throwable 轉(zhuǎn)化為 throws Exception" 一節(jié).
對(duì)于unchecked exception或error。那么它會(huì)直接被重新throw。不幸的是,它的stack trace會(huì)顯示原先產(chǎn)生exception的線程,而不是當(dāng)前傳播到的線程。一般最好的結(jié)果是把兩個(gè)線程的stack trace都包括在exception chain中,就像 get 拋出的 ExecutionException 一樣。(這其實(shí)不是 propagate 的問(wèn)題; 這是所有把 exception 在另一個(gè)線程拋出的共同問(wèn)題。)
因果鏈
Guava讓研究一個(gè)exception的因果鏈(causal chain)更簡(jiǎn)單了一些,它提供了以下3個(gè)很有用的方法,功能顧名思義:
Throwable getRootCause(Throwable)
List getCausalChain(Throwable)
String getStackTraceAsString(Throwable)