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