因為程式例外處理問題,就在前幾天龍叔的服務掛了幾秒鐘。
完了,馬上季度末打績效,竟然在這裡翻車了,心如刀絞啊。
雖然沒有影響到使用者體驗,但是找到問題並解決掉問題是工程師日常追求之一。
作為一個優秀的工程師,應該還得加幾點:
例外處理,對於每個開發者來說一點不陌生。
有人這樣描述,「一個開發者90%的時間都是在處理程式異常」。
這樣說也不算是什麼過錯,畢竟正常的邏輯總是輕鬆容易的寫完,例外處理往往會佔據開發者大多數時間。
既然這麼佔據我們的開發時間,何不主動花點時間去瞭解他,熟悉他,切莫讓他成為了最熟悉的陌生人。
在Java中,異常分為受檢查的異常,與執行時異常。兩者都在異常類層次結構中。下面的圖展示了Java異常類的繼承關係。
不難看出所有的異常都繼承自一個共同的父類別Throwable,而Throwable有兩個重要的子類:Exception(異常)和Error(錯誤)。
工程師最怕的就是Error,看到error和fail頭都大了三圈,感覺Error總是和我過不去。
Error是指Java 執行時系統的內部錯誤和資源耗盡錯誤。
應用程式不應該丟擲這種型別的物件。
如果出現了這樣的內部錯誤, 除了通告給使用者,並盡力使程式安全地終止之外, 再也無能為力了。一般這種情況很少出現。
這種錯誤會導致你的程式日常執行著,突然某天就夭折了。
異常指不期而至的各種狀況,如:檔案找不到、網路連線失敗、非法引數等。
異常是一個事件,它發生在程式執行期間,干擾了正常的指令流程。
Java通過API中Throwable類的眾多子類描述各種不同的異常。因而,Java異常都是物件,是Throwable子類的範例,描述了出現在一段編碼中的錯誤條件。
當條件生成時,錯誤將引發異常。
異常和錯誤的區別:異常能被程式本身處理,錯誤是無法處理。
異常主要分為執行期異常和非執行期異常(編譯異常)
執行期異常很好理解,就是程式跑著跑著因為觸發某個條件,導致異常發生了。比如越界了,NullPointerException等等。
編譯期異常,就是程式編譯時丟擲的異常,比如存取的檔案不存在。這類異常很好避免,編譯不會通過,不解決掉,程式就沒法執行起來。
當然有人也把異常分為可查異常和不可查異常。
可查異常 也稱之為編譯器要求必須處理的異常,一般編譯器都會檢查他,出現這類異常要麼用捕獲他,要麼丟擲他,總之必須處理他。
不可查異常 編譯器沒法檢查的,必須靠程式設計師去主動檢查,然後處理掉他。
分類的方法不是很重要,怎樣分取決於你處於某種情況下,最終都是要明白這些異常,並處理它。
上面基本都明白了java異常是什麼,以及有哪些異常,下面我們就來聊聊用什麼樣的機制去處理這些異常。
八字方針 丟擲異常,捕捉異常
throw 語句用於拋出異常,throws 語句用於宣告可能會出現的異常。
舉個例子:
public Integer division(int x, int y) {
if (y == 0)
throw new ArithmeticException("丟擲算術異常"); //丟擲異常
return x / y;
}
throws丟擲異常的規則:
2)必須宣告方法可丟擲的任何可查異常(checked exception)。即如果一個方法可能出現受可查異常,要麼用try-catch語句捕獲,要麼用throws子句宣告將它丟擲,否則會導致編譯錯誤
3)僅當丟擲了異常,該方法的呼叫者才必須處理或者重新丟擲該異常。當方法的呼叫者無力處理該異常的時候,應該繼續丟擲,而不是囫圇吞棗。
4)呼叫方法必須遵循任何可查異常的處理和宣告規則。若覆蓋一個方法,則不能宣告與覆蓋方法不同的異常。宣告的任何異常必須是被覆蓋方法所宣告異常的同類或子類。
java採用try-catch-finally語句來對異常進行捕獲並處理。
try{
//可能產生異常的程式碼
}catch (Exception e){
//例外處理邏輯
}
try{
//可能產生異常的程式碼
}catch (Exception e){
//例外處理邏輯
}finally {
//必須執行的邏輯
}
這語法大家應該在熟悉不過了,算了,龍叔還是囉嗦一遍。
try語句塊:該語句塊中是程式正常情況下應該要完成的功能,而這些程式碼中可能會產生異常,其後面的catch語句塊就是用來捕獲並處理這些異常的。
catch語句塊:該語句塊用來捕獲並處理try語句塊中產生的異常。
每個catch語句塊宣告其能處理的一種特定型別的異常,catch後面的括號中就是該特定型別的異常。
在Java7以前,每個catch語句塊只能捕獲一種異常,從Java7開始就支援一個catch捕獲多種異常,多個異常之間用|
隔開。
try{
//可能會產生異常的程式碼
}
catch(Exception1 | Exception2 |... | Exception_n e1){
//統一處理的異常程式碼
}
finally{
//通常是釋放資源的程式碼
}
finally塊:無論是否捕獲或處理異常,finally塊裡的語句都會被執行。當在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行。
在以下4種特殊情況下,finally塊不會被執行:
1)在finally語句塊中發生了異常。
2)在前面的程式碼中用了System.exit()退出程式。
3)程式所在的執行緒死亡。
4)關閉CPU。
try、catch、finally語句塊的執行順序:
到這裡大家基本明白了異常怎麼來的,怎麼處理的,接下來說一個常見的異常遮蔽問題。
一般情況下都是try中進行捕捉可能出現的異常,catch對異常進行處理,finally中進行一些資源關閉工作。
正常情況倒也沒啥說的,但咋就怕異常情況啊。
舉個例子:
try{
//可能會產生異常的程式碼
}
catch(Exception1 | Exception2 |... | Exception_n e1){
//統一處理的異常程式碼
}
finally{
//通常是釋放資源的程式碼
in.close();
out.close();
}
是不是日常都這麼寫,看起來蠻正常的。
如果我們的finally語句塊中也丟擲異常,會怎麼辦?
public Integer division(int x, int y) throws Exception {
try{
return x/y;
}catch (ArithmeticException e){
System.out.println(e.getMessage());
throw new ArithmeticException("算術異常");
}finally {
System.out.println("釋放資源");
throw new Exception("釋放資源異常");
}
}
例如這段程式碼,本意是想丟擲算術運算異常 ,結果丟擲了釋放資源異常。
由於異常資訊的丟失,異常遮蔽可能會導致某些bug變得極其難以發現,會讓你加班加到心態崩潰的。
這就是遮蔽異常,如何解決這種遮蔽異常?
有人看了上面的程式碼,又發現了另一個問題。try中有return語句,finally語句還會不會執行?
這個問題很好,答案是會執行,並且在方法返回撥用者前執行。
Java 1.7中新增的try-with-resource語法糖來很好的解決這種因為關閉資源引起的異常遮蔽問題。
public void testExcep(){
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream(new File("in.txt")));
out = new BufferedOutputStream(new FileOutputStream(new File("out.txt")));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
為了釋放資源,我們不得不這樣寫。但當我們熟悉try-with-resource語法,我們可以這樣寫。
public static void main(String[] args) {
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(new File("in.txt")));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
//處理輸入資料並輸出
} catch (IOException e) {
//捕捉異常並處理
}
}
在try子句中能建立一個資源物件,當程式的執行完try-catch之後,執行環境自動關閉資源。
程式碼寫起來簡潔,也會解決掉遮蔽異常問題。
當然也要注意,在使用try-with-resource的過程中,一定需要了解資源的close
方法內部的實現邏輯。否則還是可能會導致資源洩露。
怎麼樣,是不是很簡單呢?學會了我們一起去裝逼
算術異常類:ArithmeticExecption
空指標異常類:NullPointerException
型別強制轉換異常:ClassCastException
陣列負下標異常:NegativeArrayException
陣列下標越界異常:ArrayIndexOutOfBoundsException
檔案已結束異常:EOFException
檔案未找到異常:FileNotFoundException
字串轉換為數位異常:NumberFormatException
運算元據庫異常:SQLException
輸入輸出異常:IOException
方法未找到異常:NoSuchMethodException
這些都是非常常見的異常,當然還有一些其他異常,大家要在日常工作中及時總結,寫到你的小本本上。
今天的內容就到這裡了,有幫助記得點個贊👍。
我是龍叔,我們下期見✌️。