異常的分類
在Java中,程序執行中的異常分為Exception
與Error
,他們都繼承自Throwable
(語法錯誤跟邏輯錯誤那不叫異常)
Error
-
錯誤,JVM系統內部錯誤虛擬機無法解決的問題、資源耗盡等嚴重情況,比如:
-
無限迴圈產生堆疊溢位(
Stack Overflow
) -
寫錯分配導致記憶體不足(
Out-Of-Memory
),
-
-
解決方法就是把它寫對
Exception
-
例外,發生了出乎預料的事,又依"受不受檢"分成
-
Checked Exception
:又稱編譯時異常,通常在原始碼中必須顯式地catch並且處理,比如:-
IOException
、讀取文件不存在 -
ClassNotFoundException
-
這部分在compile time就會檢查
-
-
Unchecked Exception
:又稱RuntimeException
,運行時異常,比如:NullPointerException
,空指針訪問ArrayIndexOutOfBoundsException
,數組角標越界ClassCastException
,類型轉換異常NumberFormatException
,數字類型不合InputMismatchException
,輸入數據不符合,例如scan int
結果來了字串ArithmeticException
,算法異常,例如把某數除以0- 通常是透過撰寫相應程式以避免的邏輯錯誤, 可以根據當下的情境來判斷是不是要
catch
異常處理
代碼執行時,一旦出現異常,就會在異常處生成一個對應異常類的物件,並將其拋出,拋出後的代碼就不再執行
try-catch
-
格式:
try { int num = Integer.parseInt(str); } catch (NumberFormatException e) { e.printStackTrace(); // 印出異常 } catch (Exception e) { // something }
-
使用
try{...}
將可能有異常的代碼段包起來,當運行中異常發生時,走到哪停在哪,且生成一個異常的物件 -
使用
catch
預測可能生成的異常物件類,當有對應的就進入處理(類似於switch-case結構) -
catch
想抓的異常類型,如果有子父類關係,必須從小到大,否則報錯 -
在
try
結構中聲明的變量,只能在try
中使用,出了try
就不能再被調用
try-catch-finally
-
格式:
@org.junit.Test public void test1() { int n = method(); System.out.println(n); } public int method() { try { int[] arr = new int[3]; System.out.println(arr[5]); System.out.println("異常發生之後的語句"); // 不會執行 return 1; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("抓到對應的異常類"); // 先執行 return 2; } finally { System.out.println("必執行"); // 後執行 return 3; // 最後真正返回的值 } }
-
finally
不是必要的 -
當使用
finally
時,只要真的有異常,則其中的語句必定會被執行,即使前面的異常處理有return
、throw new
之類也不管,總之finally
就是要墊底走一趟 -
像是資料庫連接、IO輸出入、網路socket等資源,JVM不能自動回收的,我們需要手動寫資源釋放,就會把這塊放在
finally
中 -
try-catch-finally
可以嵌套使用,例如我要關掉資料庫的連接,上面try
區包了一個連接用的物件conn,出了try區想在finally
區寫"conn.close()
“卻編譯報錯不給用,只好再用一個try-catch
把finally
包起來。舉例:FileInputStream file = null; try { // 操作... } catch (FileNotFoundException e) { } finally { try { if (file != null) { file.close(); } } catch (IOException e) { } }
throws+異常類型
-
格式:聲明在方法之後,指明這個方法執行時,可能會拋出的異常類型,舉例:
public void register(int id) throws Exception,RuntimeException{}
-
拋出的異常類型可以是複數
-
體會:只是將異常拋給方法的調用者,適合用在方法有層層遞進關係的程式碼中,達成一個集中處理提高可讀性。但拋到最後總要有個接鍋俠去try-catch真正解決問題
-
如果父類的方法沒有拋出,則子類重寫的方法也不能拋出,只能在原地try-catch
-
子類重寫的方法,拋出的異常類型不能比父類本來拋出的類型還大(兒子犯的錯,要老爸罩得住)
手動拋出異常
-
想要手動的把某些狀況讓JVM當作是異常來處理,使用關鍵字**
throw new 異常類
**,注意沒有s -
舉例:
public class Test { public static void main(String[] args) { Student s1 = new Student(); try { s1.register(-5); // 當異常發生時,不希望以下的代碼被執行,所以在方法手動拋出 System.out.println("生成成功,ID為" + s1.id); } catch (Exception e) { // e.printStackTrace(); System.out.println(e.getMessage()); } } } class Student { public int id; public void register(int id) throws Exception { if (id > 0) { this.id = id; } else { throw new Exception("數據非法!"); // 這邊手動拋 // 或使用throw new RuntimeException("數據非法!"); // 就不需要上面方法處的thorws,但就只是單純報錯停下而無處理 } } }
自定義異常類
舉例:
public class MyException extends RuntimeException {
// 身分證號
static final long serialVersionUID = -70348971907457669L;
// 構造器
public MyException() {
}
public MyException(String msg) {
super(msg);
}
}
小結
throw
:通常跟new
一起聲明在方法體內,表示在此必定拋出一個異常類的物件,只要執行到這就拋異常並且撒手不幹了,必須有誰來處理throws
:是方法能夠拋出異常的聲明,表明調用此方法時可能拋出的異常種類(可以是複數),誰調用此方法就必須考慮可能發生異常,自己看著辦吧- 兩者都是消極的異常處理,只是拋出或表明可能拋出異常,真正處理終究是需要一個try-catch去處置
案例比喻
-
當"小朋友衝到馬路上"時,
throw
一個異常,停止當前方法 -
public void 照顧小朋友() throws KidsException { if (小朋友衝到馬路上=true) { throw new KidsException("出事了阿北"); // 手動拋出異常 } else { // 安全,繼續做別的事 } } // KidsException是一個自訂的異常類,繼承自Exception // 如果是RuntimeException通常不會顯式的用try-catch處理 // 運行時異常也不會被要求在方法聲明時顯式的throws
-
throws
是用來修飾"照顧小朋友"這個方法,表示預期某些異常可能發生 -
完整的處裡流程:我執行照顧小朋友方法,若發生了異常我就將其
catch
起來,呼叫父母處理,實際解決異常 -
try { 我.照顧小朋友(); // 此方法用throws修飾了,表示我預期某些異常可能發生 } catch (KidsException e) { e.printStackTrace(); 我.呼叫父母(); // 實際解決異常 }
上次修改於 2021-12-01