歡迎光臨
每天分享高質量文章

Java異常有多慢?

(點選上方公眾號,可快速關註)


來源:ImportNew – 曹旭東

本文是回答StackOverflow上的問題,但因為寫太長了,所以就發到這裡了。

http://stackoverflow.com/q/299068/315943

實際上,真正要討論的問題並不是,“相對‘那些不會發生錯誤的程式碼’來說,‘那些以異常形式上報的錯誤’會有多慢?”,因為你可能也認同“已接受的回答”。相反,真正的問題是,“相對‘那些以其他形式上報的錯誤’來說,‘那些以異常形式上報的錯誤’會有多慢?”

通常認為,“不要丟擲你想要捕獲的異常”。所以,丟擲一個其他人——如平臺或框架API——要捕獲的異常是合適的。或者在編寫一些工具API時,丟擲異常也可以的,如日誌記錄或訊息傳送,這些操作需要處理外部虛擬機器的錯誤,例如檔案IO或網路IO錯誤。

這是適合丟擲異常的例子,應該沒有人會在這些例子上有爭議。現在,看一下簡單方法中出現錯誤時會發生什麼。假設方法簽名如下:

/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the transformed instance,

 *         or null if the transformation was unsuccessful

 */

public SomeOtherClass transform(SomeClass input)

呼叫該方法的程式碼如下所示:

SomeClass input = … // get the input from somewhere

SomeOtherClass result = transform(input);

if(result == null) {

   // handle the failure

} else {

   // use the result

}

但現在,當方法傳回null時,我們想知道哪裡出現錯誤了。簡單來說可以這樣:

/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the transformed instance, never null

 * @throws TransformationException on failure

 */

public SomeOtherClass transform(SomeClass input) throws TransformationException

 

為了實現這個功能,你需要將“return null”改為“return new TransformationException(“reason”);”。呼叫方法需要做改動麼?

SomeClass input = … // get the input from somewhere

try {

   SomeOtherClass result = transform(input);

   // use the result

} catch(TransformationException e) {

   // handle the failure

}

沒有人會去讀上面的程式碼塊,沒什麼意義。所以也沒什麼可驚訝的。你可能每天都在寫類似的程式碼,但也說不上是“程式碼異味”。可是,將來你會明白在“已預料到”的錯誤上使用異常是多麼大的錯誤假設有一天你開始讀到在“已預料到”的錯誤上使用異常是非常不好的。現在,捕獲“未預料到”異常是非常可笑的,因為編寫catch程式碼塊,並顯式的處理異常本身就是預料到會有異常。但沒關係,我們還可以修改程式碼改變這種窘境。於是,我們退回到了C語言時代,傳回一個異常值來表示錯誤。

/**

 * Transforms SomeClass into SomeOtherClass.

 * @param input some class instance

 * @return the SomeOtherClass instance or, 

 *    if the transform fails, a TransformFailure instance 

 */

public Object transform(SomeClass input)

於是,呼叫部分的程式碼也不得不奇葩一些:

SomeClass input = … // get the input from somewhere

Object result = transform(input);

if(result instanceof SomeOtherClass) {

   SomeOtherClass success = (SomeOtherClass)result;

   // use the result

} else {

   TransformFailure failure = (TransformFailure)result;

   // handle the failure

}

所以,如果你覺著使用異常有程式碼異味,但可以接受打破型別安全,那麼你最終要面對的是難以維護,沒法使用,僅僅比基於異常的解決方案快兩倍的程式碼。但是你又不能接受型別安全被破壞,因為這2倍的效能提升還未被證明,現在就用實在太魯莽。所以,你決定使用結果物件,而不是傳回異常值。

/**

  * Transforms SomeClass into SomeOtherClass.

  * @param input some class instance

  * @return the TransformResult instance

  */

 public TransformResult transform(SomeClass input)

現在,相應地,呼叫部分的程式碼變成了這樣:

SomeClass input = … // get the input from somewhere

TransformResult result = transform(input);

if(result.isSuccess()) {

   SomeOtherClass success = result.getSuccess();

   // use the result

} else {

   TransformFailure failure = result.getFailure();

   // handle the failure

}

現在,我們有了一個隱晦,但可管理的解決方案。可是,它比使用異常的第一個方案慢2倍,即使你將TransformResult和TransformFailure合併也是一樣的。再說明一遍,使用結果物件比使用異常慢,即使在呼叫過程中發生了錯誤。每次你都需要建立一個新的結果物件,這沒什麼實際意義,而異常物件只在發生錯誤的時候才會建立。

對於異常,還有一個要討論的地方。假設有人在使用方法transform時,沒有認真看javadoc。在使用異常的例子中,會有下麵的程式碼:

SomeClass input = … // get the input from somewhere

SomeOtherClass result = transform(input);

// use the result

在使用異常的例子中,他們知道傳回值的型別,以及是否一個“已檢查異常”,他們可能會得到一個編譯時錯誤,或者他們會在throws陳述句中宣告相應的異常。即使是“未檢查異常”,錯誤會傳遞到上層呼叫。現在,考慮使用異常傳回值的例子:

SomeClass input = … // get the input from somewhere

SomeOtherClass result = (SomeOtherClass)transform(input);

// use the result

這個粗心的使用者寫的程式碼看起來挺漂亮,但當執行過程中發生錯誤時,就滿不是那麼回事了。那時,你費儘力氣提供的錯誤資訊會因為發生了ClassCastException異常為全部丟失。使用結果物件也不會好到哪去。

SomeClass input = … // get the input from somewhere

SomeOtherClass result = transform(input).getSuccess();

// use the result

再說一遍,上面的程式碼看來相當正常。如果他們盲目使用本文中給出的第一個方法,那麼在程式執行過程中,肯定會出現NullPointerException異常。

這裡主要想說的是,處理邏輯錯誤時,使用異常的例子可以按預想的方式正常工作,報告錯誤資訊。但是其他的解決方案卻會產生一些沒用的異常,即使你已經正確將軟體重新部署了一遍,它仍然會出錯,只有這時,你才能得到錯誤資訊。

所以,唯一符合邏輯性的結論是,如果你想上報錯誤資訊,那麼就應該使用異常。它比結果物件效能高,比異常傳回值安全,而且執行穩定。

看完本文有收穫?請轉發分享給更多人

關註「ImportNew」,提升Java技能

贊(0)

分享創造快樂