Java的異常算是Java語言的一個特點了。也是在平常編碼中會常常使用到的東西。但你真的瞭解異常嗎?html
這裏有一些關於異常的經典面試題:java
若是以上問題的答案你都能瞭然與胸,那麼恭喜你,已經很熟悉Java異常這一塊了。面試
若是一些問題還弄不清楚?不要緊,看完這篇文章就能夠了。ide
先上圖函數
拋開下面那些異常不談,咱們的關注點可能主要在四個類上:編碼
其中,由於Error
表明「錯誤」,多爲比較嚴重的錯誤。若是你瞭解JVM,應該對OutOfMemoryError
和StackOverflowError
這兩個類比較熟悉。spa
通常咱們在寫代碼時,可能用的比較多的是Exception
類和RuntimeException
類。.net
那究竟是繼承Exception
類好仍是繼承RuntimeException
類好呢?後面咱們在「編寫異常的最佳實踐」小節會講到。日誌
Java7對異常作了兩個改進。第一個是try-with-resources,第二個是catch多個異常。code
所謂的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");
}
}
複製代碼
若是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
!!!
finally
塊拋出異常或者返回值printStackTrace