兩類異常
Java中的異常繼承體系為:
這里的異常( Exception)分為兩大類:Checked異常和Runtime異常岸夯。所有的RuntimeException
類及其子類的實例被稱為Runtime異常敌土;不是RuntimeException
類及其子類的實例則被稱為Checked異常飘弧。
Java認為Checked異常都是可以被處理(修復)的異常竿裂,所有Java程序必須顯式處理Checked異常。如果程序沒有處理Checked異常冷冗,則該程序無法通過編譯(這是Checked異常與Runtime異常最大的不同)若厚。
Checked異常體現(xiàn)了Java的設計哲學——沒有完善處理的代碼根本不會被執(zhí)行!
對Checked異常的處理有兩種方式:
- 如果當前方法明確知道如何處理該異常迫靖,程序應該使用
try...catch
塊來捕獲異常院峡,然后在對應的catch
塊中處理該異常; - 如果當前方法不知道如何處理該異常系宜,則在定義方法時必須聲明拋出異常照激。
提示:在Eclipse中,可以使用快捷鍵對Checked異常進行處理蜈首,即選擇try...catch
塊來捕獲異呈德眨或者throws
聲明拋出異常欠母。
使用throws聲明拋出異常
使用throws
聲明拋出異常的思路為:當前方法不知道如何處理該異常欢策,該異常應該由上級調(diào)用者處理吆寨;如果main
方法也不知道如何處理該異常,也可以使用throws
聲明拋出異常踩寇,將該異常交給JVM處理啄清。JVM處理異常的方法則簡單粗暴得多:打印異常的跟蹤棧信息,并中止程序運行俺孙。
throws
聲明拋出只能在定義方法時使用辣卒,如果要拋出多個異常類,多個異常類用逗號隔開:
throws ExceptionClass1, ExceptionClass2...
下面是一個簡單的例子:
public class ThrowTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
}
}
由于程序所在的目錄中沒有文件“a.txt”睛榄,而且main
方法使用throws
聲明拋出異常荣茫,將異常交給JVM處理,所以程序遇到了異常后便打印異常的跟蹤棧信息场靴,并結(jié)束程序:
如果某段代碼中調(diào)用了一個帶throws
聲明拋出的方法啡莉,該方法聲明拋出了Checked異常,則表明該方法希望它的調(diào)用者來處理該異常旨剥。也就是說咧欣,調(diào)用該方法時要么放在try
塊中顯式捕獲該異常,要么放在另一個帶throws
聲明拋出的方法中轨帜∑枪荆看下面的例子:
public class ThrowTest2 {
public static void main(String[] args) throws Exception {
test();
}
public static void test() throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
}
}
對兩個throws
聲明拋出的解釋是:因為test()
方法聲明拋出IOException
異常,所以調(diào)用該方法的代碼要么處于try...catch
塊中蚌父,要么處于另一個帶throws
聲明拋出的方法中哮兰;因為FileInputStream
的構(gòu)造器聲明拋出IOException
異常,所以調(diào)用該方法的代碼要么處于try...catch
塊中苟弛,要么處于另一個帶throws
聲明拋出的方法中喝滞。
使用throws
聲明拋出異常時同樣有限制:
- 子類方法聲明拋出的異常應該是父類方法聲明拋出的異常類型的子類或相同;
- 子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多嗡午。
Checked異常的優(yōu)劣
Checked異常有兩大不便之處:
- 對于Checked異常囤躁,Java程序的處理更加復雜,增加了編程的復雜度荔睹;
- 如果在方法中顯示聲明拋出Checked異常狸演,將會導致方法簽名與異常耦合,如果該方法是重寫父類方法僻他,則該方法拋出的異常還會受到限制宵距。
當然,既然Checked異常存在吨拗,必然有其合理性:Checked異常能在編譯時提醒程序員代碼可能存在的問題满哪,提醒程序員必須注意處理該異常婿斥,或者聲明該異常由該方法的調(diào)用者來處理,從而可以避免程序員因為粗心而忘記處理該異常哨鸭。
使用throw拋出異常
異常是一種很“主觀”的說法民宿,很多時候系統(tǒng)是否要拋出異常,可能根據(jù)應用的業(yè)務需求來決定像鸡。這時候如果因與業(yè)務需求不符而產(chǎn)生異常活鹰,只能有程序員自行決定拋出,系統(tǒng)是無法拋出的(因為并不是“客觀”意義上的異常)只估。
throw
語句拋出的不是異常實例志群,而是一個異常實例,而且每次只能拋出一個異常實例:
throw ExceptionInstance;
來看下面的例子:
try {
int b = 0;
if (b == 0) {
throw new Exception("除數(shù)不能為零蛔钙!");
}
} catch (Exception e) {
System.out.println("除數(shù)不能為零锌云!");
}
當Java程序運行時接收到開發(fā)者自行拋出的異常時,同樣會中止當前的執(zhí)行流吁脱,跳到該異常對應的catch
塊桑涎,由catch
塊來處理該異常。
throw
語句拋出的異常如果是Checked異常豫喧,則該throw
語句要么處于try
塊里石洗,顯式捕獲該異常,要么放在一個帶throws
聲明拋出的方法中紧显,把該異常交給該方法的調(diào)用者處理讲衫;如果是Runtime異常,則該語句無須放在try
塊里孵班,也無須放在帶throws
聲明拋出的方法中涉兽。程序既可以顯式使用try...catch
來捕獲并處理該異常,也可以完全不理會該異常篙程,把它交給該方法的調(diào)用者處理枷畏。來看下面的例子:
public class ThrowTest {
public static void main(String[] args) {
try {
//調(diào)用聲明拋出Checked異常的方法
//要么顯式捕獲該異常
//要么在main方法中再次聲明拋出
throwChecked(3);
} catch (Exception e) {
System.out.println(e.getMessage());
}
//調(diào)用聲明拋出Checked異常的方法
//既可以顯式捕獲該異常
//也可以不理會該異常
throwRuntime(4);
}
public static void throwChecked(int a) throws Exception {
if (a > 0) {
throw new Exception("a的值大于0,不符合要求虱饿!");
}
}
public static void throwRuntime(int a) {
if (a > 0) {
throw new RuntimeException("a的值大于0拥诡,不符合要求!");
}
}
}
程序拋出異常:
上圖中第一句話是Checked異常的詳細描述氮发,而下面三行紅字則是Runtime異常的跟蹤棧信息渴肉。
注意:throw
與throws
雖然只相差一個字母,用法卻千差萬別爽冕,使用時一定要注意區(qū)分仇祭!
自定義異常類
通常情況下,程序很少自行拋出系統(tǒng)異常(因為這樣沒有必要)颈畸。在拋出異常時乌奇,應該選擇合適的異常類没讲,從而可以明確地描述該異常情況。在這種情形下礁苗,程序需要拋出自定義異常爬凑。
自定義異常類都應該繼承Exception
類,如果希望自定義Runtime異常寂屏,則應該繼承RuntimeException
類贰谣。自定義異常類時需要提供兩個構(gòu)造器:一個是無參的構(gòu)造器娜搂,另一個帶一個字符串參數(shù)的構(gòu)造器迁霎,這個字符串作為該異常對象的描述信息(也就是異常對象的getMessage()
方法的返回值):
public class AuctionException extends Exception{
//無參的構(gòu)造器
public AuctionException(){
};
//帶一個字符串參數(shù)的構(gòu)造器
public AuctionException(String msg){
//通過super關(guān)鍵字調(diào)用父類的構(gòu)造器
//將此字符串參數(shù)傳給異常對象的message屬性
super(msg);
}
}
如果要自定義Runtime異常,只需將上面代碼中的父類Exception
改為RuntimeException
百宇。
catch和throw同時使用
在實際應用中考廉,往往需要對異常采取復雜的處理方式——但一個異常出現(xiàn)時,單靠某個方法無法完全處理該異常携御,必須由幾個方法協(xié)作才可以完全處理該異常昌粤。也就是說,在異常出現(xiàn)的當前方法中啄刹,程序只對異常進行部分處理涮坐,還有些處理需要在該方法的調(diào)用者中才能完成,所以應該再次拋出異常誓军,讓該方法的調(diào)用者也能捕獲到異常袱讹。來看下面的例子:
public class AuctionTest {
private double initPrice = 3.0;
public void bid(String bidPrice) throws AuctionException {
double d = 0.0;
try {
d = Double.parseDouble(bidPrice);
} catch (Exception e) {
//控制臺打印異常的跟蹤棧信息
e.printStackTrace();
//再次拋出自定義異常
throw new AuctionException("競拍價必須是數(shù)值,不能含有其他字符昵时!");
}
if (initPrice > d) {
throw new AuctionException("競拍價比起拍價低捷雕,不允許競拍!");
}
}
public static void main(String[] args) {
AuctionTest at = new AuctionTest();
try {
at.bid("df");
} catch (AuctionException ae) {
//再次捕獲到bid()方法中的異常壹甥,并對該異常進行處理
System.err.println(ae.getMessage());
}
}
}
程序拋出異常:
提示:這種catch
和throw
結(jié)合使用的情況在大型企業(yè)級應用中非常常用救巷。企業(yè)級應用對異常的處理通常分成兩部分:應用后臺需要通過日志來記錄異常發(fā)生的詳細情況;應用還需要根據(jù)異常向應用使用者傳達某種提示句柠。在這種情形下浦译,所有異常都需要兩個方法共同完成,也就必須將catch
和throw
結(jié)合使用溯职。
Java的異常跟蹤棧
異常對象的printStackTrace()
方法用于打印異常的跟蹤棧信息精盅,根據(jù)printStackTrace()
方法的輸出結(jié)果,開發(fā)者可以找到異常的源頭缸榄,并跟蹤到異常一路觸發(fā)的過程渤弛。來看下面的例子:
class SelfException extends RuntimeException {
SelfException() {
};
SelfException(String msg) {
super(msg);
}
}
public class PrintStackTraceTest {
public static void main(String[] args) {
firstMethod();
}
public static void firstMethod() {
secondMethod();
}
public static void secondMethod() {
thirdMethod();
}
public static void thirdMethod() {
throw new SelfException("自定義異常信息");
}
}
程序拋出異常:
由上圖可知,異常從thirdMethod
方法開始觸發(fā)甚带,開始傳到secondMethod
方法她肯,再傳到firstMethod
方法佳头,最后傳到main
方法,在main
方法終止晴氨,這個過程就是Java的異常跟蹤棧康嘉。
異常的傳播規(guī)律是:只要異常沒有被完全捕獲(包括異常額沒有被捕獲,或異常處理后重新拋出了新異常)籽前,異常從發(fā)生異常的方法逐漸向外傳播亭珍,首先傳給該方法的調(diào)用者,該方法的調(diào)用者再次傳給其調(diào)用者......直到最后傳給main
方法枝哄,如果main
方法依然沒有處理該異常肄梨,JVM會中止該程序,并打印異常的跟蹤棧信息挠锥。
上圖的異常跟蹤棧信息十分清晰地記錄了程序執(zhí)行停止的各個點:第一行的信息詳細顯示了異常的類型和詳細信息众羡;接下累跟蹤棧記錄程序中所有的異常發(fā)生點,各行顯示被調(diào)用方法中執(zhí)行的停止位置蓖租,并表明類粱侣、類中的方法名、與故障點對應的文件的行蓖宦。
下面的例子展示了多線程程序中發(fā)生異常的情形:
public class ThreadExceptionTest implements Runnable {
public void run() {
firstMethod();
}
public void firstMethod() {
secondMethod();
}
public void secondMethod() {
int a = 5;
int b = 0;
int c = a / b;
}
public static void main(String[] args) {
new Thread(new ThreadExceptionTest()).start();
}
}
程序拋出異常:
由上圖可知齐婴,程序在Thread
的run
方法中出現(xiàn)了ArithmeticException
異常,這個異常的源頭是ThreadExceptionTest
的secondMethod
方法稠茂,這個異常傳播到Thread類的
run`方法就會結(jié)束柠偶。
異常處理規(guī)則
- 不要過度使用異常
異常只應該用于處理非正常的情況,不要使用異常處理來代替正常的流程控制主慰。對于一些完全剋可預知嚣州、而且處理方式清楚的錯誤,程序應該提供相應的錯誤處理代碼共螺,而不是將其籠統(tǒng)地稱為異常该肴。 - 不要使用過于龐大的
try
塊
把大塊的try
塊分割成多個可能出現(xiàn)異常的程序段落,并把它們放在單獨的try
塊中藐不,從而分別捕獲異常匀哄。 - 避免使用
Catch All
語句
所謂Catch All
語句指的是一種異常捕獲模塊,它可以處理程序發(fā)生的所有可能異常雏蛮。這種處理方式有兩個弊端:無法對不同的異常分情況處理(如果要分情況處理涎嚼,則需要在catch
塊中使用分支語句進行控制,得不償失)挑秉;容易“壓制”異撤ㄌ荩或者忽略“關(guān)鍵”異常。
Catch All
語句:
try{
//可能引發(fā)Checked異常的代碼
} catch(Throwable t) {
//進行異常處理
t.printStackTrace();
}
- 不要忽略捕獲到的異常
既然捕獲到了異常,那catch
塊就理應做些有用的事情立哑,即對異常采取適當?shù)拇胧禾幚懋惓R共选⒅匦聮伋鲂庐惓;蛘咴诤线m的層處理異常铛绰。