導(dǎo)語
學(xué)完異常的捕獲及處理就懂的情書嵌屎。
// 情書
// 理解包容全部的你
try {
we.together(time); // 和你在一起的時(shí)間
} catch(Exception e) { // 接收到所有在一起的問題
i.understandYou(); // 我理解你
i.containYou(); // 我包容你
} finally {
we.love++; // 不管發(fā)生什么,我們的愛都會(huì)越來越多
}
主要內(nèi)容
- 異常的產(chǎn)生以及對(duì)于程序的影響
- 異常處理的格式
- 異常的處理流程(核心)
- throw跨新、throws關(guān)鍵字的使用
- 異常處理的使用標(biāo)準(zhǔn)(重要代碼模型)
- 自定義異常
具體內(nèi)容
異常指程序運(yùn)行過程中出現(xiàn)的非正常現(xiàn)象,例如用戶輸入錯(cuò)誤膏潮、除數(shù)為零罐柳、需要處理的文件不存在掌腰、數(shù)組下標(biāo)越界等。所謂異常處理张吉,就是指程序在出現(xiàn)問題時(shí)依然可以正確的執(zhí)行完齿梁。
異常是Java的一個(gè)重大特色,合理的使用異常處理,可以讓我們程序更加的健壯勺择。
異常的產(chǎn)生
異常是導(dǎo)致程序中斷執(zhí)行的一種指令流创南,異常一旦出現(xiàn)并且沒有合理處理的話,那么程序就會(huì)將中斷執(zhí)行省核。
范例:產(chǎn)生異常的代碼
public class TestDemo {
public static void main(String args[]) {
System.out.println("1稿辙、除法計(jì)算開始。");
System.out.println("2气忠、除法計(jì)算邻储。" + (10 / 0)); // 除數(shù)不能為0,所以會(huì)產(chǎn)生異常
System.out.println("3旧噪、除法計(jì)算結(jié)束吨娜。");
}
}
會(huì)出現(xiàn)異常:
java.lang.ArithmeticException
一旦產(chǎn)生異常之后,產(chǎn)生異常的語句以及之后的語句將不再執(zhí)行 默認(rèn)情況下是進(jìn)行異常信息的輸出淘钟,而后自動(dòng)結(jié)束程序的執(zhí)行宦赠。
我們要做的事情是:即使出現(xiàn)了異常,那么也應(yīng)該讓程序正確的執(zhí)行完畢米母。
異常的處理
如果想要進(jìn)行異常的處理勾扭,在Java之中提供了三個(gè)關(guān)鍵字:try、catch爱咬、finally尺借,而這三個(gè)關(guān)鍵字的使用語法如下所示。
try{
// 有可能出現(xiàn)異常的語句
} [catch(異常類型 對(duì)象) {
// 處理異常
} catch(異常類型 對(duì)象) {
// 處理異常
} catch(異常類型 對(duì)象) {
// 處理異常
} ... ] [finally {
// 不管是否出現(xiàn)異常精拟,都執(zhí)行的統(tǒng)一代碼
}]
范例:應(yīng)用異常的處理
public class TestDemo {
public static void main(String args[]) {
System.out.println("1燎斩、除法計(jì)算開始。");
try {
System.out.println("2蜂绎、除法計(jì)算:" + (10 / 0)); // 除數(shù)不能為0栅表,所以會(huì)產(chǎn)生異常
System.out.println("############");
} catch(ArithmeticException e) {
System.out.println("******出現(xiàn)異常******");
}
System.out.println("3、除法計(jì)算結(jié)束师枣。");
}
}
輸出結(jié)果:
1怪瓶、除法計(jì)算開始。
******出現(xiàn)異常******
3践美、除法計(jì)算結(jié)束洗贰。
由于使用了異常處理,這樣即使程序中出現(xiàn)了異常陨倡,發(fā)現(xiàn)也可以正常的執(zhí)行完畢敛滋。
出現(xiàn)的異常的目的是為了解決異常,所以為了能夠進(jìn)行異常的處理兴革,可以使用異常類中提供的printStackTrace()方法绎晃,進(jìn)行異常信息的完整輸出蜜唾。
范例:使用printStackTrace()方法
public class TestDemo {
public static void main(String args[]) {
System.out.println("1、除法計(jì)算開始庶艾。");
try {
System.out.println("2袁余、除法計(jì)算:" + (10 / 0));
} catch(ArithmeticException e) {
e.printStackTrace();
}
System.out.println("3、除法計(jì)算結(jié)束咱揍。");
}
}
輸出結(jié)果:
1颖榜、除法計(jì)算開始。
java.lang.ArithmeticException: / by zero at TestDemo.main(TestDemo.java:5)
3述召、除法計(jì)算結(jié)束朱转。
此時(shí)發(fā)現(xiàn)打印的異常信息是很完整的。
范例:使用finally
public class TestDemo {
public static void main(String args[]) {
System.out.println("1积暖、除法計(jì)算開始。");
try {
System.out.println("2怪与、除法計(jì)算:" + (10 / 0));
} catch(ArithmeticException e) {
System.out.println("******出現(xiàn)異常******");
} finally {
System.out.println("###不管是否出現(xiàn)異常我都執(zhí)行夺刑!###");
}
System.out.println("3、除法計(jì)算結(jié)束分别。");
}
}
輸出結(jié)果:
1遍愿、除法計(jì)算開始。
******出現(xiàn)異常******
###不管是否出現(xiàn)異常我都執(zhí)行耘斩!###
3沼填、除法計(jì)算結(jié)束。
public class TestDemo {
public static void main(String args[]) {
System.out.println("1括授、除法計(jì)算開始坞笙。");
try {
System.out.println("2、除法計(jì)算:" + (10 / 2));
} catch(ArithmeticException e) {
System.out.println("******出現(xiàn)異常******");
} finally {
System.out.println("###不管是否出現(xiàn)異常我都執(zhí)行荚虚!###");
}
System.out.println("3薛夜、除法計(jì)算結(jié)束。");
}
}
輸出結(jié)果:
1版述、除法計(jì)算開始梯澜。
2、除法計(jì)算:5
###不管是否出現(xiàn)異常我都執(zhí)行渴析!###
3晚伙、除法計(jì)算結(jié)束。
在異常捕獲的時(shí)候發(fā)現(xiàn)俭茧,一個(gè)try語句后同可以跟著多個(gè)catch語句咆疗。
范例:觀察程序
public class TestDemo {
public static void main(String args[]) {
System.out.println("1、除法計(jì)算開始恢恼。");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2民傻、除法計(jì)算:" + (x / y));
} catch(ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("###不管是否出現(xiàn)異常我都執(zhí)行!###");
}
System.out.println("3、除法計(jì)算結(jié)束漓踢。");
}
}
以上的程序?qū)⒂捎脩糨斎氩僮鲾?shù)據(jù)牵署,于是可能存在有如下的情況出現(xiàn):
- 用戶執(zhí)行的時(shí)候不輸入?yún)?shù)(java.lang.ArrayIndexOutOfBoundsException數(shù)組越界)。
- 用戶輸入的參數(shù)不是數(shù)字(java.lang.NumberFormatException數(shù)字格式)喧半。
- 被除數(shù)為0(java.lang.ArithmeticException計(jì)算)奴迅。
范例:加入多個(gè)catch
public class TestDemo {
public static void main(String args[]) {
System.out.println("1、除法計(jì)算開始挺据。");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2取具、除法計(jì)算:" + (x / y));
} catch(ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch(NumberFormatException e) {
e.printStackTrace();
} catch(ArithmeticException e) {
e.printStackTrace();
} finally {
System.out.println("###不管是否出現(xiàn)異常我都執(zhí)行!###");
}
System.out.println("3扁耐、除法計(jì)算結(jié)束暇检。");
}
}
程序現(xiàn)在的確很健壯,所有可能出現(xiàn)的異常都處理完了婉称。
以上的異常都已經(jīng)知道了块仆,還讓它出現(xiàn),這絕對(duì)是技術(shù)問題王暗。
異常的處理流程(核心)
通過以上的分析應(yīng)該已經(jīng)掌握了異常的處理格式了悔据,但是遺憾的是,以上的操作并不是最好的異常處理方法俗壹,所以我們必須清楚整個(gè)Java中異常的處理流程科汗。
首先來觀察兩個(gè)異常類的繼承結(jié)構(gòu):
ArithmeticException | NumberFormatException |
---|---|
java.lang.Object --java.lang.Throwable ----java.lang.Exception ------java.lang.RutimeException --------java.lang.ArithmeticException |
java.lang.Object --java.lang.Throwable ----java.lang.Exception ------java.lang.RuntimeException --------java.lang.IllegalArgumentException ----------java.lang.NumberFormatException |
經(jīng)過異常類的觀察可以發(fā)現(xiàn)所有的異常類都是Throwable的子類。而在Throwable下有兩個(gè)子類:
- Error:指的是JVM錯(cuò)誤绷雏,指此時(shí)的程序還沒有執(zhí)行头滔,如果沒有執(zhí)行用戶無法處理。
- Exception:指的是程序運(yùn)行中產(chǎn)生的異常之众,用戶可以處理拙毫。
也就是所謂的異常處理指的就是所有Exception以及它的子類異常。
異常處理流程:
1棺禾、當(dāng)程序在運(yùn)行的過程之中出現(xiàn)了異常后缀蹄,那么會(huì)由JVM自動(dòng)根據(jù)異常的類型實(shí)例化一個(gè)與之類型匹配的異常類對(duì)象(此處用戶不用去關(guān)心new,由系統(tǒng)自動(dòng)負(fù)責(zé)處理)膘婶。
2缺前、產(chǎn)生了異常對(duì)象之后會(huì)判斷當(dāng)前的語句上是否存在有異常處理,如果現(xiàn)在沒有異常處理悬襟,那么就交給JVM進(jìn)行默認(rèn)的異常處理衅码,處理的方式:輸出異常信息,而后結(jié)束程序的調(diào)用脊岳。
3逝段、如果此時(shí)存在有異常的捕獲操作垛玻,那么會(huì)由try語句來捕獲產(chǎn)生的異常類實(shí)例化對(duì)象,而后與try語句之后的每一個(gè)catch進(jìn)行比較奶躯,如果現(xiàn)在有符合的捕獲類型帚桩,則使用當(dāng)前catch的語句來進(jìn)行異常的處理,如果不匹配嘹黔,則向下繼續(xù)匹配其它的catch账嚎。
4、不管最后異常處理是否能夠匹配儡蔓,那么都要向后執(zhí)行郭蕉,如果此時(shí)程序中存在有finally語句,那么就先執(zhí)行finally中的代碼喂江,但是執(zhí)行完畢后需要根據(jù)之前的catch匹配結(jié)果來決定如何執(zhí)行召锈,如果之前已經(jīng)成功的捕獲了異常,那么就繼續(xù)執(zhí)行获询,finally之后的代碼烟勋,如果之前沒有成功的捕獲異常,那么就將此異常交給JVM默認(rèn)處理筐付。
整個(gè)過程就好比方法重載一樣。根據(jù)catch后面的參數(shù)類型進(jìn)行匹配阻肿,但是所有Java對(duì)象都存在有自動(dòng)的向上轉(zhuǎn)型的操作支持瓦戚,也就是說如果要真的匹配類型,簡(jiǎn)單的做法是匹配Exception就夠了丛塌。
范例:使用Exception處理異常
public class TestDemo {
public static void main(String args[]) {
System.out.println("1较解、除法計(jì)算開始。");
try {
int x = Integer.parseInt(args[0]);
int y = Integer.parseInt(args[1]);
System.out.println("2赴邻、除法計(jì)算:" + (x / y));
} catch(Exception e) {
e.printStackTrace();
} finally {
System.out.println("###不管是否出現(xiàn)異常我都執(zhí)行印衔!###");
}
System.out.println("3、除法計(jì)算結(jié)束姥敛。");
}
}
此時(shí)所有的異常都使用了Exception進(jìn)行處理奸焙,所以在程序之中不用再去關(guān)心到底使用哪一個(gè)異常。
兩點(diǎn)說明:
- 在編寫多個(gè)catch捕獲異常的時(shí)候彤敛,捕獲范圍大的異常一定要放在捕獲范圍小的異常之后与帆,否則程序編譯錯(cuò)誤。
- 雖然直接捕獲Exception比較方便墨榄,但是這樣也不好玄糟,因?yàn)樗械漠惓6紩?huì)按照同樣一種方式進(jìn)行處理。所以在一些要求嚴(yán)格的項(xiàng)目里面袄秩,異常一定要分開處理會(huì)更好阵翎。
throws關(guān)鍵字
throws關(guān)鍵字主要用于方法的聲明上逢并,指的是當(dāng)我們方法之中出現(xiàn)異常后交由被調(diào)用處來處理。
范例:使用throws
class MyMath {
// 由于存在有throws郭卫,那么就表示此方法里產(chǎn)生的異常交給被調(diào)用處處理
public static int div(int x, int y) throws Exception {
return x / y;
}
}
調(diào)用以上的方法(錯(cuò)誤示范)
public class TestDemo {
public static void main(String args[]) {
System.out.println(MyMath.div(10, 2));
}
}
代碼改為
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println(MyMath.div(10, 2));
} catch(Exception e) {
e.printStackTrace();
}
}
}
如果調(diào)用了具有throws聲明的方法之后砍聊,那么不管操作是否出現(xiàn)異常,都必須使用try...catch來進(jìn)行異常的處理箱沦。
在程序之中主方法也屬于方法辩恼,那么主方法上能否繼續(xù)使用throws來拋出異常呢?
public class TestDemo {
public static void main(String args[]) throws Exception {
// 表示此異常產(chǎn)生之后會(huì)直接通過主方法拋出
System.out.println(MyMath.div(10, 0));
}
}
在主方法上如果繼續(xù)拋出了異常谓形,那么這個(gè)異常就將交給JVM進(jìn)行處理灶伊,也就是默認(rèn)處理方法,輸出異常信息寒跳,而后結(jié)束程序調(diào)用 聘萨。
主方法上不要加上throws,因?yàn)槌绦蛉绻鲥e(cuò)了童太,也希望可以正常的結(jié)束調(diào)用米辐。
throw關(guān)鍵字
在程序之中可以直接使用throw手工的拋出一個(gè)異常類的實(shí)例化對(duì)象。
范例:手工拋出異常
public class TestDemo {
public static void main(String args[]) {
throw new Exception("自己定義的異常书释!");
}
}
編譯錯(cuò)誤
錯(cuò)誤:未報(bào)告的異常錯(cuò)誤Exception翘贮,必須對(duì)其進(jìn)行捕獲或聲明以便拋出throw new Exception("自己定義的異常!");
修改代碼
public class TestDemo {
public static void main(String args[]) {
try {
throw new Exception("自己定義的異常爆惧!");
} catch(Exception e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果
java.lang.Exception:自己定義的異常狸页!
異常肯定應(yīng)該盡量回避扯再,為什么要自己拋出異常呢芍耘?
throw和throws的區(qū)別:
- throw:指的是在方法之中人為拋出一個(gè)異常類對(duì)象。
- throws:在方法的聲明上使用熄阻,表示此方法在調(diào)用時(shí)必須處理異常斋竞。
異常處理標(biāo)準(zhǔn)格式(重要代碼模型)
現(xiàn)在要求定義一個(gè)div()方法,要求秃殉,這個(gè)方法在進(jìn)行計(jì)算之前打印提示信息坝初,在計(jì)算結(jié)束完畢也打印提示信息,如果在計(jì)算之中產(chǎn)生了異常复濒,剛交給被調(diào)用處進(jìn)行處理脖卖。
范例:上面要求
先寫出不出錯(cuò)的情況
class MyMath {
public static int div(int x, int y) {
int result = 0;
System.out.println("***1、除法計(jì)算開始***");
result = x / y;
System.out.println("***2巧颈、除法計(jì)算結(jié)束***");
return result;
}
}
public class TestDemo {
public static void main(String args[]) {
System.out.println(MyMath.div(10, 2));
}
}
輸出結(jié)果:
***1畦木、除法計(jì)算開始***
***2、除法計(jì)算結(jié)束***
5
對(duì)于以上給出的除法操作不可能永遠(yuǎn)都正常的完成砸泛,所以應(yīng)該進(jìn)行一些合理的處理十籍,首先某個(gè)方法出現(xiàn)異常了必須交給被調(diào)用處處理蛆封,那么應(yīng)該在方法上使用throws拋出。
修改代碼
class MyMath {
// 此時(shí)表示div()方法上如果出現(xiàn)了異常交給被調(diào)用處處理
public static int div(int x, int y) throws Exception {
int result = 0;
System.out.println("***1勾栗、除法計(jì)算開始***");
result = x / y;
System.out.println("***2惨篱、除法計(jì)算結(jié)束***");
return result;
}
}
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println(MyMath.div(10, 2));
} catch(Exception e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
***1、除法計(jì)算開始***
***2围俘、除法計(jì)算結(jié)束***
5
如果代碼出錯(cuò)了呢
class MyMath {
// 此時(shí)表示div()方法上如果出現(xiàn)了異常交給被調(diào)用處處理
public static int div(int x, int y) throws Exception {
int result = 0;
System.out.println("***1砸讳、除法計(jì)算開始***");
result = x / y;
System.out.println("***2、除法計(jì)算結(jié)束***");
return result;
}
}
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
***1界牡、除法計(jì)算開始***
java.lang.ArithmeticException...
如果代碼出錯(cuò)了呢簿寂?程序有些內(nèi)容就不執(zhí)行了,這樣明顯不對(duì)宿亡。
修改代碼
class MyMath {
// 此時(shí)表示div()方法上如果出現(xiàn)了異常交給被調(diào)用處處理
public static int div(int x, int y) throws Exception {
int result = 0;
System.out.println("***1常遂、除法計(jì)算開始***");
try {
result = x / y;
} catch(Exception e) {
throw e;
} finally {
System.out.println("***2、除法計(jì)算結(jié)束***");
}
return result;
}
}
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
***1挽荠、除法計(jì)算開始***
***2克胳、除法計(jì)算結(jié)束***
java.lang.ArithmeticException...
實(shí)際上以上的代碼還可以縮寫(不建議使用)。
class MyMath {
// 此時(shí)表示div()方法上如果出現(xiàn)了異常交給被調(diào)用處處理
public static int div(int x, int y) throws Exception {
int result = 0;
System.out.println("***1圈匆、除法計(jì)算開始***");
try {
result = x / y;
} finally {
System.out.println("***2漠另、除法計(jì)算結(jié)束***");
}
return result;
}
}
public class TestDemo {
public static void main(String args[]) {
try {
System.out.println(MyMath.div(10, 0));
} catch(Exception e) {
e.printStackTrace();
}
}
}
如果現(xiàn)在你直接使用了try...finally,那么表示你連處理一下的機(jī)會(huì)都沒有跃赚,就直接拋出了酗钞。
RuntimeException類
先觀察一個(gè)程序代碼
public class TestDemo {
public static void main(String args[]) {
int temp = Integer.parseInt("100"); // 將字符串變?yōu)檎蛿?shù)據(jù)
}
}
現(xiàn)在來觀察一下parseInt()方法的定義
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
此時(shí)parseInt()方法拋出了NumberFormatException,按照道理來講来累,應(yīng)該進(jìn)行強(qiáng)制性的異常捕獲,可是現(xiàn)在并沒有這種強(qiáng)制性的要求窘奏。
來觀察一下NumberFormatException的繼承結(jié)構(gòu)
java.lang.Object
--java.lang.Throwable
----java.lang.Exception
------java.lang.RuntimeException // 運(yùn)行時(shí)異常
--------java.lang.IllegalArgumentException
----------java.lang.NumberFormatException
在Java里面為了方便用戶代碼的編寫嘹锁,專門提供了一種RuntimeException類,這種異常類的最大特征在于:程序在編譯的時(shí)候不會(huì)強(qiáng)制性的要求用戶處理異常着裹,用戶可以根據(jù)自己的需要選擇性進(jìn)行處理领猾,但是如果沒有處理又發(fā)生異常了,將交給JVM默認(rèn)處理骇扇。也就是說RuntimeException的子異常類摔竿,可以由用戶根據(jù)需要選擇進(jìn)行處理。
解釋Exception與RuntimeException的區(qū)別:
- Exception是RuntimeException的父類少孝。
- 使用Exception定義的異常必須要被處理继低,而RuntimeException的異常可以選擇性處理稍走。
常見的RuntimeException:
- ArithmeticException
- NullPointerException
- ClassCastException
assert關(guān)鍵字(了解)
assert關(guān)鍵字是在JDK1.4的時(shí)候引入的袁翁,其主要功能是進(jìn)行斷言柴底。
在Java中的斷言指的是程序執(zhí)行到某行代碼處于一定是預(yù)期的結(jié)果。
范例:觀察斷言
public class TestDemo {
public static void main(String args[]) {
int num = 10;
// 中間可能經(jīng)過了20行代碼來操作num的內(nèi)容
期望中的內(nèi)容應(yīng)該是20
assert num == 20 : "num的內(nèi)容不是20";
System.out.println("num = " + num);
}
}
輸出結(jié)果:
num = 10
默認(rèn)情況下斷言是不應(yīng)該影響程序的運(yùn)行的粱胜,也就是說在java解釋程序的時(shí)候柄驻,斷言是默認(rèn)不起作用的。
啟用斷言
java -ea TestDemo
輸出結(jié)果:
Exception in thread "main" java.lang.AssertionError: num的內(nèi)容不是20...
在Java里面斷言的設(shè)計(jì)要比C++強(qiáng)的很多焙压,它不會(huì)影響到程序的執(zhí)行鸿脓,但是使用的意義不大。
自定義異常
Java本身已經(jīng)提供了大量的異常涯曲,但是這些異常在實(shí)際的工作之中往往并不夠去使用野哭,例如:當(dāng)你要執(zhí)行數(shù)據(jù)增加操作的時(shí)候,有可能會(huì)出現(xiàn)一些錯(cuò)誤的數(shù)據(jù)掀抹,而這些錯(cuò)誤的數(shù)據(jù)一旦出現(xiàn)就應(yīng)該拋出異常虐拓,例如:AddException,這樣的異常Java并沒有傲武,所以就需要由用戶自己去開發(fā)一個(gè)自己的異常類蓉驹。
如果想要開發(fā)自定義的異常類可以選擇繼承Exception或者是RuntimeException。
范例:定義AddException
class AddException extends Exception {
public Addexception(String msg) {
super(msg);
}
}
public class TestDemo {
public static void main(String args[]) {
int num = 20;
try {
if(num > 10) { // 出現(xiàn)了錯(cuò)誤揪利,應(yīng)該產(chǎn)生異常
throw new AddException("數(shù)值傳遞的過大态兴!");
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
輸出結(jié)果:
AddException:數(shù)值傳遞的過大!...
這種代碼只能介紹自定義異常的形式疟位,但是并不能說明自定義異常的實(shí)際使用瞻润。
總結(jié)
- Exception的父類是Throwable,但是在編寫代碼的時(shí)候盡量不要使用Throwable甜刻,因?yàn)門hrowable下面還包含了一個(gè)Error子類绍撞,我們能夠處理的只有Exception子類。
- 異常處理的標(biāo)準(zhǔn)格式:try得院、catch傻铣、finally、throw祥绞、throws非洲。
- RuntimeException與Exception的區(qū)別。