一、 Java中的異常處理機制
1.1 Java中異常的概念
在Java程序運行過程中出現的錯誤統稱爲異常。
例子:算術異常,通常是被零除時發生的異常,是運行時異常
public class ExceptionTest01 {
public static void main(String[] args){
int i = 30; int j = 0; //被零除,會拋出算術異常 int k = i/j; } } |
控制檯顯示:
Exception in thread "main" java.lang.ArithmeticException: / by zero at com.weixin.test1.ExceptionTest01.main(ExceptionTest01.java:10) |
例子:經典的空指針異常,當引用爲null是發生的異常,是運行期異常
public class ExceptionTest02 {
public static void main(String[] args){
Student stu = null; //stu沒有引用Student類型的對象,會拋出空指針異常 stu.print("張三", 20); } }
class Student{
public void print(String name,int age){
System.out.println("name: " + name + " age: " + age); } }
|
控制檯顯示:
Exception in thread "main" java.lang.NullPointerException at com.weixin.test1.ExceptionTest02.main(ExceptionTest02.java:9) |
例子:數字格式化異常,通常是把非數字字符串轉換成數值類型是發生的異常,是運行時異常
public class ExceptionTest03 {
public static void main(String[] args){
String str = "abc"; //將字符串abc轉換成整型值會拋出數字格式化異常 int i = Integer.parseInt(str); } } |
控制檯顯示:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc" at java.lang.NumberFormatException.forInputString(Unknown Source) at java.lang.Integer.parseInt(Unknown Source) at java.lang.Integer.parseInt(Unknown Source) at com.weixin.test1.ExceptionTest03.main(ExceptionTest03.java:9) |
1.1.1 異常的概念
通過上面的例子可以看出,Java中的異常就是在程序運行過程中出現的非正常情況,類似與我們日常生活中出現的不可預知的情況,例如:出租車上學的過程中出租車突然出現的故障讓我們遲到等,就是正常情況下不會發生的事件,但是他發生了。當發生異常時,正常的情況會發生變化,在Java中程序會終止繼續執行並退出虛擬機。
例子:當發生異常情況時,Java虛擬機拋出異常信息後,程序會終止運行
public class ExceptionTest04 {
public static void main(String[] args){
int i = 20; int j = 0;
int k = i/j; //當發生異常時,程序終止執行 System.out.println("程序執行到這裏了嗎!"); } } |
控制檯顯示:
Exception in thread "main" java.lang.ArithmeticException: / by zero at com.weixin.test1.ExceptionTest04.main(ExceptionTest04.java:10) |
1.1.2 異常的作用
當程序在運行過程中發生異常,Java虛擬機會將發生異常的相關信息輸出到控制檯,便於程序員對代碼進行修正。如果沒有異常機制,當程序由於異常而終止運行時,程序員很難判斷程序中斷執行的原因。異常機制可以使編寫的程序更加健壯。
例子:通過異常情況,程序員可以修改代碼,完善代碼,使程序更健壯
public class ExceptionTest05 {
public static void main(String[] args){
Student stu = null;
if(stu != null){ stu.print("張三", 20); } else { System.out.println("對象stu爲空,沒有指向任何引用!"); } } } |
.
控制檯顯示:
對象stu爲空,沒有指向任何引用! |
例子:在代碼中實現防止被零除
1.2 Java中異常的分類
在Java中,採用類的形式模擬現實生活中的突發情況,如在上面的例子中,採用Java類java.lang.NumberFormatException來說明在程序運行過程中出現了數值類格式化異常,Java類java.lang.NullPointerException代表出現了空對象調用成員時出現的異常。
在Java中,每一類異常由一個特定的類來代表,如java.lang.NullPointerException異常類代表空指針異常類,但是需要注意的是,所有異常類的對象通常情況下不是由用戶來創建的,而是在程序運行過程中,當出現了異常情況時,由Java虛擬機創建這個異常類型的對象賦給一個引用。這個異常對象就是一個具體的異常事件,在對象中保存了發生異常的詳細信息。
1.2.1 Java中異常類的繼承關係
在Java中異常類的繼承關係:
Throwable:異常類的父類
Error:錯誤,不可處理的異常,當發生Error時,程序直接退出,如:內存泄漏、斷電
Exception:異常,必須進行處理的異常
RuntimeException:運行時異常,編譯時不會提示,可控制的異常,不要求必須處理
編譯時異常:程序編輯階段就會提示的異常
1.2.2 Java中異常的分類
在Java中,異常分爲三類:錯誤、運行時異常(可控)和編譯時異常(不可控)
1. 錯誤,不可以處理的異常,當發生錯誤時,無法修復,直接退出運行,如內存溢出。
2. 運行時異常,在編譯期不會提示,如上面的例子,不要求必須進行處理的異常。所有RuntimeException類的子類都是運行時異常。運行時異常發生機率較低,因爲這一類異常通常情況下只要完善代碼就可以避免。
3. 編譯時異常,此類異常必須進行處理,在編譯期就會提示,如果不處理,編譯將無法通過。所有Exception類的子類都是編譯時異常,除RuntimeException類及其子類。編譯時異常發生機率較高。這一類異常在代碼中是無法控制的,如:讀取的文件不在指定的目錄,要加載的類不存在等等,就是說這種類型的異常在代碼中是無法解決的。
例子:編譯時異常必須處理,否則編譯不能通過
public class ExceptionTest06 {
public static void main(String[] args){ //會提示要處理java.lang.ClassNotFoundException異常 //如果不處理異常,編譯不能通過 Class.forName("java.lang.String"); } } |
1.3 Java中異常的捕獲與處理
對於Exception直接子類的異常類型必須進行處理,這一類異常稱爲編譯時異常。運行時異常不需要在編譯期進行處理。對於編譯期異常的處理方式分爲兩種:
1. 對可能存在的異常採取捕獲並處理的方式
2. 對異常不採取捕獲並處理的方式,而是將可能發生的異常類型繼續聲明和拋出,讓其他調用者捕獲並處理。
1.3.1 聲明和拋出
方法可以通過throws關鍵字聲明此方法可能出現的異常,格式如下:
public void method() throws 異常類型名稱{…} |
注意:聲明的異常可以是運行時異常也可以是編譯時異常,但是如果聲明的是編譯時異常,調用方法處必須對聲明的異常進行處理,或者繼續通過throws關鍵字進行聲明拋出而不做處理,將異常繼續向調用處進行傳遞。最終必須有一個調用處對異常進行處理。或者由Java虛擬機來處理。
例子:對於聲明的異常是編譯時異常,調用此方法的方法中必須對異常進行處理或繼續對異常進行聲明拋出
如果聲明的是運行時異常,方法調用處不需要顯示的對異常進行處理。下面通過例子說明。
例子:聲明拋出的異常是運行時異常,調用此方法的方法中不需要對異常進行處理,因爲這類異常發生率較低,並且可以通過優化代碼避免異常的發生
public class ExceptionTest07 {
public static void main(String[] args){
Test07 test = new Test07(); test.print(); } }
class Test07{
public void print() throws RuntimeException{
System.out.println("測試運行時異常採用聲明的方式"); } } |
通過throws命令將異常的處理權限向調用處進行傳遞,可以一級一級的向下傳遞,但是最終調用處要對異常進行處理,特殊的,如果主方法也同樣聲明且不處理,異常會被虛擬機處理。要注意運行時異常和編譯時異常的區別,運行時異常對是否處理異常沒有硬性的規定,所以不處理,程序也能通過編譯。
例子:聲明拋出的編譯時異常調用處必須進行處理或繼續拋出,否則編譯不能通過
public class ExceptionTest08 {
public static void main(String[] args){
Test08 test = new Test08(); //主方法沒有聲明異常,所以必須對異常進行處理,此處編譯不能通過 //test.print(); } }
class Test08{ //方法調用的print(String name)方法,同樣也沒有處理異常, //採用throws關鍵字繼續聲明異常,將異常處理權向下一個調用處進行傳遞 public void print() throws Exception{
System.out.println("測試聲明異常想調用處傳遞"); print("張三"); } //此方法聲明編譯時異常,並沒有處理異常,異常的處理權限向調用處進行傳遞 public void print(String name) throws Exception{
System.out.println("name: " + name); } } |
例子:聲明拋出運行時異常,調用處可以不處理,因爲運行時異常可以通過代碼修改避免異常的發生,是可控的異常
1.3.2 異常的捕捉
採用try…catch方式對異常進行攔截並處理,語法格式爲:
try{ Java語句塊; }catch(異常類型1 e1){ 處理語句塊; }catch(異常類型2 e2){ 處理語句塊; }catch(異常類型3 e3){ 處理語句塊; } |
可以有多個catch語句塊,用來攔截不同類型的異常。
當程序運行過程中發生不同類型的異常情況,執行不同類型異常的catch語句塊代碼,也就是catch語句塊攔截相應類型的異常並執行相關的語句塊。
例子:catch語句塊可以有多個,用來攔截不同類型的異常事件
如果程序發生沒有在catch中攔截的異常類型,此異常將不會被攔截。
例子:程序發生了沒有在catch語句中指定運行時異常類型,不會被攔截,程序會終止運行,注意:運行時異常不是必須進行捕獲並處理的異常
1.3.2.1 攔截相同類型的異常事件
try語句塊中的所有編譯時異常必須在catch中進行攔截。如果存在沒有攔截處理的編譯時異常,程序編譯不能通過。或者通過throws關鍵字進行聲明拋出。
例子:編譯時異常必須進行攔截或者通過throws進行聲明拋出
public class ExceptionTest09 {
public static void main(String[] args){
try{ Class.forName("abc"); FileInputStream in = new FileInputStream("abc.txt"); }catch(ClassNotFoundException e){ System.out.println(e); }catch(FileNotFoundException e) {
} System.out.println(“OK”); } } |
1.3.2.2 捕獲異常的順序
catch語句塊中的異常類從上到下要按照子類到父類的順序,否則編譯不能通過。也可以採用父類的類型捕獲子類類型的異常。
例子:catch語句的順序是從子類到父類型,否則編譯不能通過
1.3.2.3 發生異常時的執行順序
當try語句塊中的代碼發生異常情況時,程序的執行流程會直接執行攔截異常的catch語句塊中的代碼,當catch語句塊中的代碼執行結束後,會接着執行try…catch語句後面的語句,而不會接着發生異常的語句下面的語句接着執行。
例子:發生異常時,執行流程會發生改變,catch語句塊相當於對異常進行了處理,所以執行流程會從try…catch後面的語句繼續執行,而不會從發生異常的位置繼續執行,所以不要將所有的語句都放在try語句塊中
public class ExceptionTest11 {
public static void main(String[] args){
try{ Class.forName("abc");
System.out.println("執行發生異常語句後面的語句!"); }catch(ClassNotFoundException e){ System.out.println("執行catch語句塊中的代碼!"); }
System.out.println("執行try...catch語句塊後面的語句!"); } } |
控制檯顯示:
執行catch語句塊中的代碼! 執行try...catch語句塊後面的語句! |
1.3.2.4 異常類的實例
在catch語句塊中異常類的實例e,是由虛擬機在發生異常時創建並將異常信息保存到這個異常實例中,通過這個異常實例可以獲得程序發生異常的信息。
例12:異常信息保存在異常類型實例e中,通過e可以得到相關異常信息
public class ExceptionTest12 {
public static void main(String[] args){
try{ Class.forName("abc"); }catch(ClassNotFoundException e){ System.out.println(e); } } } |
控制檯顯示:
java.lang.ClassNotFoundException: abc |
可以看出java.lang.ClassNotFoundException類或者他的父類重寫了toString()方法,顯示的不是內存地址,而是保存的異常信息。
1.3.2.5 getMessage()方法
方法getMessage()方法來源於異常類的父類java.lang.Throwable,用於在控制檯打印異常信息,但是顯示的異常信息較爲簡單。
例13:異常對象的getMessage()方法給出的信息較爲簡單
public class ExceptionTest13 {
public static void main(String[] args){
try{ FileInputStream in = new FileInputStream("abc"); }catch(FileNotFoundException e){ String msg = e.getMessage(); System.out.println(msg); } } } |
控制檯顯示:
abc (系統找不到指定的文件。) |
1.3.2.6 printStackTrace()方法
方法printStackTrace()方法來源於異常類的父類java.lang.Throwable,用於在控制檯打印發生異常的堆棧信息,是開發人員常用的方法,用來調試程序。
例14:異常對象的printStackTrace()方法給出堆棧中的信息,較爲詳細
public class ExceptionTest14 {
public static void main(String[] args){
try{ FileInputStream in = new FileInputStream("abc"); }catch(FileNotFoundException e){ e.printStackTrace(); } } } |
控制檯顯示:
java.io.FileNotFoundException: abc (系統找不到指定的文件。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(Unknown Source) at java.io.FileInputStream.<init>(Unknown Source) at com.weixin.test1.ExceptionTest14.main(ExceptionTest14.java:11)
|
1.3.3 finally語句塊
在try…catch語句塊後面可以添加finally語句塊,語法格式如下:
try{ 語句塊 }catch(Exception e){ 語句塊 }finally{ 語句塊 } |
注意:finally語句塊中的語句一定會執行,不考慮特殊情況。
例15:finally語句塊中的語句一定會執行,主要作用是釋放資源,在程序發生異常時,當catch語句塊執行完畢後會接着執行finally語句塊中的語句
public class ExceptionTest15 {
public static void main(String[] args){
try{ Class.forName("abc"); System.out.println("發生異常的語句後面的語句不會執行!"); }catch(ClassNotFoundException e){ System.out.println("捕獲異常的代碼會執行!"); }finally{ System.out.println("finally語句塊中的代碼一定會執行!"); }
System.out.println("try...catch後面的語句會執行!"); } } |
控制檯顯示:
捕獲異常的代碼會執行! finally語句塊中的代碼一定會執行! try...catch後面的語句會執行! |
例16:當遇到return語句時,finally語句塊中的語句也會執行,但try…catch後面的語句將不會執行,直接跳出當前方法的執行
public class ExceptionTest16 {
public static void main(String[] args){
try{ System.out.println("程序正常執行!"); return; }catch(Exception e){ System.out.println("捕獲異常的代碼不會執行!因爲沒有發生異常"); }finally{ System.out.println("finally語句塊中的代碼一定會執行!"); }
System.out.println("try...catch後面的語句不會執行!"); } }
|
控制檯顯示:
程序正常執行! finally語句塊中的代碼一定會執行! |
例17:當遇到System.exit(0)時,系統會直接退出運行,後面的代碼都不會執行,包括finally語句塊中的內容
public class ExceptionTest17 {
public static void main(String[] args){
try{ System.out.println("程序正常執行!"); System.exit(0); }catch(Exception e){ System.out.println("捕獲異常的代碼不會執行!因爲沒有發生異常"); }finally{ System.out.println("finally語句塊中的代碼不會執行!"); }
System.out.println("try...catch後面的語句不會執行!"); } } |
控制檯不會執行finally語句塊中的語句,因爲虛擬機直接退出運行了。
1.4 自定義異常
自定義異常可以是編譯時異常也可以是運行是異常。在項目開發初期,根據項目的實際情況定義自己的異常類,如DaoException、ApplicationException,DaoException代表數據庫操作階段發生的異常,而ApplictionException是業務層發生的異常。定義自定義異常類較爲簡單,只要繼承Exception或RuntimeException類並重寫幾個構造方法即可,下面通過例子說明。
例18:定義數據庫相關操作的異常類
public class DaoException extends Exception{
public DaoException() { super(); }
public DaoException(String message) { super(message); } } |
例19:定義業務層的異常類
public class ApplicationException extends Exception{
public ApplicationException() { super(); }
public ApplicationException(String message) { super(message); } } |
1.4.1 通過throw關鍵字手動拋出異常
可以通過throw關鍵字手動拋出異常,在手動拋出異常的方法中要顯示的通過throws關鍵字聲明異常,通知調用者處理或繼續聲明異常。手動拋出的編譯時異常調用者必須進行處理或繼續聲明。而運行期異常調用者可以不進行處理。
例20:
public class ExceptionTest18 {
public static void main(String[] args){
Test18 t = new Test18(); try { t.print(); } catch (ApplicationException e) { e.printStackTrace(); } } }
class Test18{
public void print() throws ApplicationException{ FileInputStream in = null; try{ in = new FileInputStream("abc"); }catch(FileNotFoundException e){ throw new ApplicationException("訪問的文件不存在!"); }finally{ if(in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("finally語句塊中的代碼會執行!"); }
System.out.println("發生異常後,try語句塊後面的語句不會執行!"); } } |
控制檯顯示:
finally語句塊中的代碼會執行! com.weixin.test1.ApplicationException: 訪問的文件不存在! at com.weixin.test1.Test18.print(ExceptionTest18.java:27) at com.weixin.test1.ExceptionTest18.main(ExceptionTest18.java:13) |
注意:手動拋出異常必須在catch代碼塊的最後一行,並且發生異常語句後面的語句不會執行,但finally語句塊會執行。