JAVA中的異常類都繼承自Throwable類,也就是說,這是異常類的根。Throwable類擴展了兩個類Error類和Exception類,Exception類又擴展了一個RuntimeException類。如下圖: Error:稱為錯誤,由Java虛擬機生成並拋出,這類錯誤一般是運行時系統內部的 ...
- java異常是Java提供的用於處理程式中錯誤的一種機制。
- 所謂錯誤是指程式在運行過程中發生的一些異常事件(如除數為0、數組下標越界、操作的文件不存在等)。
- Java程式在執行過程中如果出現異常事件,可以生成一個異常類對象,該異常對象封裝了異常事件的信息並將被提交給Java運行時系統,這個過程稱為拋出(throw)異常。
- 當Java運行時系統收到異常對象時,會尋找能處理這一異常的代碼並將對當前異常對象交給其處理,這一過程稱為異常捕獲(catch)。
JAVA中的異常類都繼承自Throwable類,也就是說,這是異常類的根。Throwable類擴展了兩個類Error類和Exception類,Exception類又擴展了一個RuntimeException類。如下圖:
- Error:稱為錯誤,由Java虛擬機生成並拋出,這類錯誤一般是運行時系統內部的錯誤,無法被處理。
- Exception:所有異常類的父類,其子類對應了各種各樣可能出現的異常事件,一般需要用戶顯式地聲明或捕獲。如文件類異常:FileNotFoundException,IOExecption。
- RuntimeException:一種特殊的異常類,繼承自Exception類。如除數為0、數組下標越界等。這類異常產生比較頻繁,用戶一般不對其做捕獲處理,否則對程式的可讀性和運行效率影響很大,而是由系統自動檢測並將它們交給預設的異常處理程式進行處理。如ArithmeticException,ArrayIndexOutOfBoundException。
一般來說,出現RuntimeException異常表示的是代碼不合理而出現的問題。
- 未檢查異常:Error錯誤和RuntimeException類的異常;
- 已檢查異常:Exception類的異常但不包括RuntimeException類。
因此,在自定義異常類型時,大多數都直接繼承Exception類,偶爾可能繼承RuntimeException類,更偶爾的可能會繼承這些類的某些子類。
try-catch-finally結構和處理流程
使用try-catch結構捕捉異常,並設置捕捉到後的處理方式。還可以加上finally結構,這是可選結構,但卻表示try結構中必須會被執行的部分。
以下是try-catch-finally結構和處理過程的分析。
try {
// 待捕捉測試的代碼1
// 待捕捉測試的代碼2 (假設此為異常代碼,將拋出名為異常名2的異常)
// 待捕捉測試的代碼3
} catch (異常名1 對象名e) {
// 捕捉到異常名1的異常後,該做的處理代碼4
} catch (異常名2 對象名e) {
// 捕捉到異常名2的異常後,該做的處理代碼5
} ... {
//...
} finally {
//一定會執行的代碼6
}
//try結構外的代碼7
前提假設,在各代碼中沒有return子句。執行過程為:首先代碼1正常執行,到代碼2處拋出異常名2的異常,通過異常名匹配,將選擇第二個catch結構,於是將異常2封裝到對象名e中,並執行代碼5處理異常。catch部分處理完後,還有最後處理段finally,於是執行代碼6。出了finally後,還將執行代碼7。
註意,當代碼2出現異常後,代碼3不會執行。而finally則是無論是否真的捕捉到了異常、是否在catch段有return都會執行的代碼段。換句話說,finally段的代碼除了內部錯誤或外界影響都一定會執行。就像下麵的例子中,即使catch使用了return,但finally還是會執行,只不過這個catch終止了try結構外的代碼。
例如,除數為0時會拋出ArithmeticException異常。try-catch捕捉它:
public class TEx {
public static void main(String[] args) {
try {
System.out.println("[start]");
System.out.println(2/0);
System.out.println("[end]");
} catch (ArithmeticException e) {
System.out.println("[Catching]: " + e);
return;
} finally {
System.out.println("[Finally]");
}
System.out.println("[out of try-catch]");
}
}
在finally段中還可以繼續try-catch-finally,防止該段落的代碼再次拋出異常。
public class TEx {
public static void main(String[] args) {
try {
System.out.println("[start]");
System.out.println(2/0);
System.out.println("[end]");
} catch (ArithmeticException e) {
System.out.println("[Catching]: " + e);
return;
} finally {
try {
System.out.println("[Finally-try-start]");
System.out.println(3/0);
} catch (ArithmeticException e) {
System.out.println("[Finally-Catching]: " + e);
}
}
System.out.println("[out of try-catch]");
}
}
輸出異常信息
java中的異常都會封裝到對象中。異常對象中有幾個方法:
- printStackTrace():輸出最詳細的信息,包括拋出異常的行號,異常信息以及異常原因。
- getMessage():輸出異常信息。
- getCause():輸出異常原因。
異常拋出過程和throw、throws關鍵字
throw關鍵字用於在某個語句處拋出一個異常,只要執行到這個語句就表示必定拋出異常。
throws關鍵字用於在方法處拋出一個或多個異常,這表示執行這個方法可能會拋出異常。
throw OBJECT;
throw new EXCEPTION("Message");
method() throws EXCEPTION1[,EXCEPTION2...] {}
對於Exception類(非RuntimeException)的異常即已檢查異常類,在調用時要麼進行捕捉,要麼繼續向上級拋出。這類錯誤產生和處理的過程為:
- 方法f()內部的方法體的throw向上拋出給方法f();
- 方法f()的throws向上拋出拋給f()調用者;
- 方法調用者必須捕捉處理,或者不想捕捉就繼續向上拋出;
- 每一級的調用者都不想捕捉而是一直向上拋出,則最後由java虛擬機報錯:"未報告的異常錯誤XXXX必須對其進行捕獲或聲明以便拋出"。
以下是拋出異常的一個簡單示例,拋出的是ArithmeticException異常,因為是RuntimeException類異常,因此從方法體內部throw拋出後,無需在方法定義處使用throws繼續拋出。
public class EX {
void f(int n) { // 或void f(int n) throws ArithmeticException {}
if (n == 0) {
throw new ArithmeticException("hello Exception!");
} else {
System.out.println("right!");
}
}
public static void main(String[] args) {
EX m = new EX();
m.f(1);
m.f(0); // throw Exception
}
}
執行結果:
right!
Exception in thread "main" java.lang.ArithmeticException: hello Exception! //異常的信息
at EX.f(EX.java:4) //真實產生異常的地方
at EX.main(EX.java:13) //調用產生異常的地方
所以,對於RuntimeException類異常來說,是否使用throws關鍵字並無影響。一般來說,Exception類異常但非RuntimeException才需要使用throws關鍵字,因為Exception類異常必須要被捕獲並處理,而RuntimeException異常則無所謂。
例如將上面的ArimeticException改為FileNotFoundException,前者為Runtime類異常,而後者為Exception但非Runtime類異常,因此使用throw拋出後,必須在定義方法處也使用throws拋出錯誤。這一過程是"向上級拋出"的過程:"方法體內部拋出異常-->拋給方法本身"。
void f(int n) throws FileNotFoundException {
if (n == 0) {
throw new FileNotFoundException("hello Exception!"); //throw a new yichang duixiang
} else {
System.out.println("right!");
}
}
如果不使用throws關鍵字拋出錯誤,則將報錯:
EX.java:6: 錯誤: 未報告的異常錯誤FileNotFoundException; 必須對其進行捕獲或聲明以便拋出
throw new FileNotFoundException("hello Exception!"); //throw a new yichang duixiang
從方法f()拋出向上拋出給調用者後,調用者要麼使用try-catch捕捉,要麼繼續向上拋出,否則報錯。例如捕捉
import java.io.*;
public class EX {
void f(int n) throws FileNotFoundException {
if (n == 0) {
throw new FileNotFoundException("hello Exception!");
} else {
System.out.println("right!");
}
}
public static void main(String[] args) {
try {
EX m = new EX();
m.f(0);
} catch (FileNotFoundException e) {
System.out.println(e);
}
System.out.println("out of try-catch");
}
}
如果不捕捉,則可以繼續在方法處向上拋出:
public static void main(String[] args) throws FileNotFoundException {
EX m = new EX();
m.f(0);
}
拋出異常時的註意事項
throw可以同時定義可能拋出的多種異常,儘管這些異常存在繼承關係。這時在捕捉異常時,應該先捕捉子類,再捕捉父類。
例如FileNotFoundException是IOException的子類,可以同時:
throws FileNotFoundException,IOException
捕捉時應先捕捉FileNotFoundException,再IOException。
try {
...
} catch (FileNotFoundException e) {
...
} catch (IOException e) {
...
}
在重寫有throws子句的方法時,需要註意:
- 子類重寫父類方法要拋出與父類一致的異常,或者不拋出異常
- 子類重寫父類方法所拋出的Exception類異常不能超過父類的範疇
- 子類重寫父類方法拋出的異常可以超出父類範疇,但超出的部分必須是RuntimeException類的異常
所以下麵的定義中,前子類1-3重寫和子類5-7都是有效的,但子類4重寫是錯誤的。
父類:method() throws IOException {}
子類1:method() throws {}
子類2:method() throws IOException {}
子類3:method() throws FileNotFoundException {}
子類4:method() throws Exception {}
子類5:method() throws RuntimeException {}
子類6:method() throws IOException,RuntimeException {}
子類7:method() throws IOException,ArithmeticException {}
自定義異常
異常是類,當產生異常時會構建此類的異常對象。
自定義異常時需要考慮異常類的構造方法是否接參數,參數需要如何處理實現怎樣的邏輯。
自定義的異常一般都從Exception繼承。
例如下麵定義了一個銀行卡存、取錢時的程式,其中自定義了一個Exception類錯誤。
// User Define Exception
class OverDrawException extends Exception {
private double amount;
public OverDrawException(double amount) {
this.amount = amount;
}
public OverDrawException(String message) {
super(message);
}
public double getAmount() {
return amount;
}
}
// Card class
class Card {
//cardNum:卡號,balance:餘額
private String cardNum;
private double balance;
Card(String n,double b) {
this.cardNum = n;
this.balance = b;
}
//方法:存錢
public void cunQian(double n) {
balance += n;
}
//方法:取錢
public void quQian(double n) throws OverDrawException {
if (n <= this.balance) {
balance -= n;
} else {
double need = n - balance;
throw new OverDrawException(need);
}
}
//方法:返回餘額
public double getBalance() {
return this.balance;
}
}
public class SuanZhang {
public static void main(String [] args) {
try {
Card card = new Card("62202",300);
System.out.println("卡裡餘額:" + card.getBalance());
//存錢
card.cunQian(200);
System.out.println("餘額:" + card.getBalance());
//取錢
card.quQian(600);
System.out.println("餘額:" + card.getBalance());
} catch (OverDrawException e) {
System.out.println(e);
System.out.println("抱歉!您的餘額不足,缺少:" + e.getAmount());
}
}
}
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!