天天看點

Effective Java 第三版——73.抛出合乎于抽象的異常

Tips

書中的源代碼位址:https://github.com/jbloch/effective-java-3e-source-code

注意,書中的有些代碼裡方法是基于Java 9 API中的,是以JDK 最好下載下傳 JDK 9以上的版本。

73. 抛出合乎于抽象的異常

當一個方法抛出一個與它所執行的任務沒有明顯關聯的異常時,這是令人不安的。在方法傳播由低層(lower-level)抽象抛出的異常時,會經常發生這種情況。它不僅令人不安,而且用實作細節“污染”了上層的API。如果上層(higher layer)的實作在以後的版本中發生變化,那麼它抛出的異常也會發生變化,可能會破壞現有的用戶端程式。

為了避免這個問題,上層(higher layers)應該捕獲低層( lower-level )的異常,并在它們的位置抛出可以用上層級别(higher-level )抽象來解釋的異常。這個習語被稱為異常轉譯:

// Exception Translation
try {
    ... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException e) {
    throw new HigherLevelException(...);
}
           

以下的異常轉轉譯的示例是來自AbstractSequentialList類,該類是List接口的骨架實作(skeletal implementation )(條目 20)。 在此示例中,異常轉譯由

List <E>

接口中的get方法規範強制要求的:

/**
 * 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);
    }
}
           

如果較低級别的異常可能有助于調試導緻較進階别異常的問題,則需要一種稱為異常鍊(exception chaining )的特殊異常轉譯形式。低層異常(原因)傳遞給高層異常,高層異常提供一個通路器方法(Throwable的getCause方法)來檢索低層異常:

// Exception Chaining
try {
    ... // Use lower-level abstraction to do our bidding
} catch (LowerLevelException cause) {
    throw new HigherLevelException(cause);
}
           

進階異常的構造方法将原因傳遞給一個感覺鍊(chaining-aware)的父類構造方法,是以它最終被傳遞給Throwable的一個感覺鍊的構造方法,比如Throwable(Throwable):

// Exception with chaining-aware constructor
class HigherLevelException extends Exception {
    HigherLevelException(Throwable cause) {
        super(cause);
    }
}
           

大多數标準異常都有感覺鍊的構造方法。對于沒有這樣做的異常,可以使用Throwable的initCause方法設定原因。異常連結不僅允許你以程式設計方式通路原因(使用getCause),而且還将原因的堆棧跟蹤內建到更進階别異常的堆棧跟蹤中。

雖然異常轉譯優于低層異常的無意識傳播,但不應過度使用。 在可能的情況下,處理較低層異常的最佳方法是通過確定較低級别的方法成功執行來避免異常。 有時可以通過檢查更進階别方法的參數的有效性,然後再将它們傳遞到較低層來完成此操作。

如果不可能防止來自較低層的異常,那麼接下來最好的事情就是讓較高層靜默地解決這些異常,進而使較進階别方法的調用者與較低級别的問題隔離開來。 在這些情況下,使用某些适當的日志記錄工具(如java.util.logging)記錄異常可能是适當的。 這允許程式員調查問題,同時把使用者和用戶端代碼隔離開。

總之,如果無法阻止或處理較低層的異常,那麼使用異常轉譯,除非較低級别的方法恰好保證其所有異常都适用于較進階别。 異常連結提供了兩全其美的優勢:它允許抛出适當的更進階别異常,同時可以捕獲失敗分析的根本原因(條目 75)。