教你如何善用Java異常

Java的異常算是Java語言的一個特點了。也是在平常編碼中會常常使用到的東西。但你真的瞭解異常嗎?java

這裏有一些關於異常的經典面試題:面試

  • Java與異常相關的類結構和主要繼承關係是怎樣的?
  • Java7在關於異常的語法上作了什麼改進?
  • 什麼是運行時異常和聲明式異常?它們有什麼區別?
  • 什麼是「異常丟失(異常覆蓋)」問題?
  • 什麼是異常鏈?
  • 什麼是返回值覆蓋?
  • 編寫異常時的一些最佳實踐?

若是以上問題的答案你都能瞭然與胸,那麼恭喜你,已經很熟悉Java異常這一塊了。ide

若是一些問題還弄不清楚?不要緊,看完這篇文章就能夠了。函數

異常的層次結構

先上圖編碼

拋開下面那些異常不談,咱們的關注點可能主要在四個類上:spa

  • Throwable
  • Error
  • Exception
  • RuntimeException

其中,由於 Error 表明「錯誤」,多爲比較嚴重的錯誤。若是你瞭解 JVM ,應該對 OutOfMemoryError 和 StackOverflowError 這兩個類比較熟悉。code

通常咱們在寫代碼時,可能用的比較多的是 Exception 類和 RuntimeException 類。orm

那究竟是繼承 Exception 類好仍是繼承 RuntimeException 類好呢?後面咱們在「編寫異常的最佳實踐」小節會講到。對象

Java7與異常

Java7對異常作了兩個改進。第一個是 try-with-resources ,第二個是 catch多個異常 。繼承

try-with-resources

所謂的try-with-resources,是個語法糖。實際上就是自動調用資源的close()函數。和Python裏的with語句差很少。

不使用try-with-resources,咱們在使用io等資源對象時,一般是這樣寫的:

String getReadLine() throws IOException {
    BufferedReader br = new BufferedReader(fileReader);
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}
複製代碼

使用try-with-recources的寫法:

String getReadLine() throws IOException {
    try (BufferedReader br = new BufferedReader(fileReader)) {
        return br.readLine();
    }
}
複製代碼

顯然,編繹器自動在try-with-resources後面增長了判斷對象是否爲null,若是不爲null,則調用close()函數的的字節碼。

只有實現了java.lang.AutoCloseable接口,或者java.io.Closable(實際上繼隨自java.lang.AutoCloseable)接口的對象,纔會自動調用其close()函數。

有點不一樣的是java.io.Closable要求一實現者保證close函數能夠被重複調用。而AutoCloseable的close()函數則不要求是冪等的。具體能夠參考Javadoc。

可是,須要注意的是try-with-resources會出現 異常覆蓋 的問題,也就是說 catch 塊拋出的異常可能會被調用 close() 方法時拋出的異常覆蓋掉。咱們會在下面的小節講到異常覆蓋。

多異常捕捉

直接上代碼:

public static void main(String[] args) {
    try {
        int a = Integer.parseInt(args[0]);
        int b = Integer.parseInt(args[1]);
        int c = a / b;
        System.out.println("result is:" + c);
    } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) {
        System.out.println("發生了以上三個異常之一。");
        ie.getMessage();
        // 捕捉多異常時,異常變量默認有final修飾,
        // 因此下面代碼有錯:
        // ie = new ArithmeticException("test");
    }
}
複製代碼

Suppressed

若是 catch 塊和 finally 塊都拋出了異常怎麼辦?請看下下小節分析。

運行時異常和聲明式異常

所謂運行時異常指的是 RuntimeException ,你不用去顯式的捕捉一個運行時異常,也不用在方法上聲明。

反之,若是你的異常只是一個 Exception ,它就須要顯式去捕捉。

示例代碼:

void test() {
    hasRuntimeException();
    try {
        hasException();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

void hasException() throws Exception {
    throw new Exception("exception");
}

void hasRuntimeException() {
    throw new RuntimeException("runtime");
}
複製代碼

雖然從異常的結構圖咱們能夠看到, RuntimeException 繼承自 Exception 。但Java會「特殊對待」運行時異常。因此若是你的程序裏面須要這類異常時,能夠繼承 RuntimeException 。

並且若是不是明確要求要把異常交給上層去捕獲處理的話,咱們建議是 優先使用運行時異常 ,由於它會讓你的代碼更加簡潔。

什麼是異常覆蓋

正如咱們前面提到的,在 finally 塊調用資源的 close() 方法時,是有可能拋出異常的。與此同時咱們可能在 catch 塊拋出了另外一個異常。那麼 catch 塊拋出的異常就會被 finally塊的異常「吃掉」。

看看這段代碼,調用 test() 方法會輸出什麼?

void test() {
    try {
        overrideException();
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

void overrideException() throws Exception {
    try {
        throw new Exception("A");
    } catch (Exception e) {
        throw new Exception("B");
    } finally {
        throw new Exception("C");
    }
}
複製代碼

會輸出 C 。能夠看到,在 catch 塊的 B 被吃掉了。

JDK提供了Suppressed的兩個方法來解決這個問題:

// 調用test會輸出:
// C
// A
void test() {
    try {
        overrideException();
    } catch (Exception e) {
        System.out.println(e.getMessage());
        Arrays.stream(e.getSuppressed())
                .map(Throwable::getMessage)
                .forEach(System.out::println);
    }
}

void overrideException() throws Exception {
    Exception catchException = null;
    try {
        throw new Exception("A");
    } catch (Exception e) {
        catchException = e;
    } finally {
        Exception exception = new Exception("C");
        exception.addSuppressed(catchException);
        throw exception;
    }
}
複製代碼

異常鏈

你能夠在拋出一個新異常的時候,使用 initCause 方法,指出這個異常是由哪一個異常致使的,最終造成一條異常鏈。

詳情請查閱公衆號以前的關於異常鏈的文章。

返回值覆蓋

跟以前的「異常覆蓋」問題相似, finally 塊會覆蓋掉 try 和 catch 塊的返回值。

因此最佳實踐是不要在 finaly 塊使用 return !

相關文章
相關標籤/搜索