什么是異常?
- 異常本質(zhì)上是程序上的錯誤,錯誤在我們編寫程序的過程中會經(jīng)常發(fā)生池凄,包括編譯期間和運(yùn)行期間的錯誤。
- 編譯期間的錯誤通常是基礎(chǔ)的語法錯誤鬼廓,比如括號沒有正常配對肿仑、語句結(jié)束后少寫了分號,關(guān)鍵字編寫錯誤等碎税,編譯器會對這些錯誤給出提示尤慰,幫助我們進(jìn)行修訂。
- 運(yùn)行期間的錯誤只有程序運(yùn)行時才能看到錯誤的提示雷蹂,比如數(shù)組訪問時下標(biāo)越界伟端、使用空對象調(diào)用方法、算術(shù)運(yùn)算時除數(shù)為0匪煌、類型轉(zhuǎn)換時無法正常轉(zhuǎn)型等荔泳,運(yùn)行期間的錯誤往往是難以預(yù)料的。
程序中的異常
- 在程序運(yùn)行過程中虐杯,意外發(fā)生的情況玛歌,背離我們程序本身的意圖的表現(xiàn),都可以理解為異常擎椰。
- 當(dāng)程序在運(yùn)行期間出現(xiàn)了異常支子,如果置之不理,程序可能會不正常運(yùn)行达舒、強(qiáng)制中斷運(yùn)行值朋、造成用戶數(shù)據(jù)丟失、資源無法正常釋放巩搏、直接導(dǎo)致系統(tǒng)崩潰,顯然這不是我們希望看到的結(jié)果昨登。
- 利用Java中的異常處理機(jī)制,我們可以更好地提升程序的健壯性贯底。
異常的分類
在java中丰辣,通過Throwable
以及它的相關(guān)子類來描述各種不同的異常類型。
Throwable
是異常的根類禽捆,包含兩個子類Error
和Exception
Throwable類常用方法
String getMessage()
返回保存在某個異常中的描述字符串,異常參數(shù)調(diào)用
void printStackTrace()
顯示堆棧跟蹤信息
Error
-
Error
是程序無法處理的錯誤笙什,表示運(yùn)行應(yīng)用程序中較嚴(yán)重問題。 - 大多數(shù)的錯誤與代碼編寫所執(zhí)行的操作是沒有什么關(guān)系的胚想,而表示代碼運(yùn)行的時候java虛擬機(jī)出現(xiàn)的系列問題琐凭。
- 常見的有虛擬機(jī)錯誤、內(nèi)存溢出浊服、線程死鎖等统屈,這些錯誤往往是不可查的胚吁,因?yàn)樗鼈冊趹?yīng)用程序的控制和處理能力之外,而且絕大多數(shù)是程序運(yùn)行時不允許出現(xiàn)的狀況愁憔。
- 對于設(shè)計(jì)合理的應(yīng)用程序來說腕扶,即使確實(shí)發(fā)生了錯誤,本質(zhì)上也不應(yīng)該試圖去處理它所引起的異常狀況惩淳。
Exception
-
Exception
是程序本身可以處理的異常。異常處理通常指的是針對這類異常的處理 -
Exception
類的異常包括Unchecked Exception
和Checked Exception
-
Unchecked Exception
(非檢查異常):編譯器不要求強(qiáng)制處理的異常乓搬,包含RuntimeException
以及它的相關(guān)子類 -
Checked Exception
(檢查異常):編譯器要求必須處理的異常思犁,除了RuntimeException
以及它的相關(guān)子類其他的Exception
子類都是檢查異常,如IOException
进肯、SQLException
常見的異常類型
異常處理
在Java應(yīng)用程序中激蹲,異常處理機(jī)制為:拋出異常、捕獲異常
JVM默認(rèn)處理機(jī)制
JVM有一個默認(rèn)的異常處理機(jī)制江掩,即將該異常的名稱学辱、異常的信息、異常出現(xiàn)的位置打印在了控制臺上环形,同時將程序停止運(yùn)行
拋出異常
- 當(dāng)一個方法出現(xiàn)錯誤引發(fā)異常時策泣,方法創(chuàng)建異常對象并交付運(yùn)行時系統(tǒng)進(jìn)行處理。
- 異常對象通常包含異常類型和異常出現(xiàn)時的程序狀態(tài)等信息抬吟。
- 運(yùn)行時系統(tǒng)負(fù)責(zé)尋找處置異常的代碼并執(zhí)行萨咕。
- 在方法拋出異常之后,運(yùn)行時系統(tǒng)將轉(zhuǎn)為尋找合適的異常處理器火本。
- 運(yùn)行時系統(tǒng)從發(fā)生異常的方法開始危队,依次回查調(diào)用棧中的方法,當(dāng)異常處理器所能處理的異常類型與方法拋出的異常類型相符時钙畔,即為合適的異常處理器茫陆。
- 當(dāng)運(yùn)行時系統(tǒng)遍歷調(diào)用棧而未找到合適的異常處理器,則運(yùn)行時系統(tǒng)終止擎析。同時簿盅,意味著Java程序的終止。
捕獲異常
對于運(yùn)行時異常揍魂、錯誤或可查異常挪鹏,java技術(shù)所要求的異常處理方式有所不同。
Java規(guī)定對于可查異常必須捕獲愉烙、或者聲明拋出讨盒,而允許忽略不可查的RuntimeException
(含子類)和Error
(含子類)
實(shí)現(xiàn)
主要通過5個關(guān)鍵字來實(shí)現(xiàn):try
、catch
步责、finally
返顺、throw
禀苦、throws
try-catch-finally
語法格式:
try塊:用于捕獲異常
catch塊:用于處理try捕獲到的異常
finally塊:無論是否發(fā)生異常代碼總能執(zhí)行
try
塊后可接零個或多個catch
塊,如果沒有catch
塊遂鹊,則必須跟一個finally
塊
執(zhí)行流程
- 我們將可能產(chǎn)生異常的代碼放入
try
塊當(dāng)中振乏,當(dāng)try
塊中代碼沒有異常發(fā)生時,catch
塊中的內(nèi)容不會被執(zhí)行秉扑,而直接執(zhí)行之后的代碼慧邮。 - 當(dāng)
try
塊發(fā)生異常時,會產(chǎn)生一個異常對象且當(dāng)該類型能與catch
塊中的異常類型正常匹配時舟陆,程序就會進(jìn)入到catch
塊執(zhí)行相應(yīng)的處理邏輯误澳,然后順序執(zhí)行下去。 - 當(dāng)出現(xiàn)異常且無法正常匹配處理則程序中斷運(yùn)行
示例
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數(shù)秦躯,接受用戶的鍵盤輸入忆谓,輸出兩數(shù)之商
Scanner input=new Scanner(System.in);
System.out.println("=====運(yùn)算開始=====");
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
System.out.println("one和two的商是:"+ (one/two));
System.out.println("=====運(yùn)算結(jié)束=====");
}
}
程序運(yùn)行結(jié)果
當(dāng)我們輸入的是非數(shù)字時,程序會報(bào)錯并終止運(yùn)行踱承,報(bào)錯的異常為InputMismatchException
當(dāng)我們輸入的第二個數(shù)為0時倡缠,程序也會報(bào)錯并終止運(yùn)行,報(bào)錯的異常為ArithmeticException
使用try-catch-finally對異常進(jìn)行捕獲處理
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數(shù)茎活,接受用戶的鍵盤輸入昙沦,輸出兩數(shù)之商
Scanner input=new Scanner(System.in);
try {
System.out.println("=====運(yùn)算開始=====");
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
System.out.println("one和two的商是:"+ (one/two));
} catch (Exception e) {
System.out.println("程序出錯啦~~~~");
e.printStackTrace(); //打印錯誤的堆棧信息
}finally{
System.out.println("=====運(yùn)算結(jié)束=====");
}
}
}
此時如果發(fā)生上述任意一種錯誤時,程序會執(zhí)行catch
塊中的處理邏輯载荔,然后繼續(xù)往下執(zhí)行直到程序結(jié)束桅滋;
這里catch
塊中的異常類型為Exception
,是所有可處理異常類的基類身辨,所以出現(xiàn)任何類型異常都能匹配上丐谋;
而定義在finally
塊中的邏輯一定會被執(zhí)行,不管程序是否出現(xiàn)異常煌珊。
嵌套try-catch塊
如果內(nèi)部try代碼塊產(chǎn)生的異常沒有被與該try對應(yīng)的catch捕獲号俐,會傳到外部try代碼塊。
使用外部try捕獲大部分嚴(yán)重錯誤的同時定庵,讓內(nèi)部try代碼塊處理不太嚴(yán)重的錯誤吏饿。
使用多重catch結(jié)構(gòu)處理異常
當(dāng)程序可能會產(chǎn)生多種類型的異常,針對可能出現(xiàn)的不同異常如果希望做不同的處理蔬浙,那么就可以使用多重catch
注意多重catch
塊中的異常類型不能一致猪落,且捕獲父類型的catch
塊應(yīng)該在子類型的后面,比如Exception
應(yīng)該在最后面
注意事項(xiàng)
- 一旦某個
catch
捕獲到匹配的異常類型畴博,將進(jìn)入異常處理代碼笨忌。一經(jīng)處理結(jié)束,就意味著整個try-catch
語句結(jié)束俱病,其他的catch
子句不再有匹配和捕獲異常類型的機(jī)會官疲。 - 對于有多個
catch
子句的異常程序而言袱结,應(yīng)該盡量將捕獲底層異常類的catch
子句放在前面,同時盡量將捕獲相對高層的異常類的catch子句放在后面途凫。否則,捕獲底層異常類的catch
子句將可能會被屏蔽垢夹。
示例
public class TryDemo {
public static void main(String[] args) {
//定義兩個整數(shù),接受用戶的鍵盤輸入维费,輸出兩數(shù)之商
Scanner input=new Scanner(System.in);
try{
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
System.out.println("one和two的商是:"+ (one/two));
}catch(ArithmeticException e){
System.exit(1);
System.out.println("除數(shù)不允許為零");
e.printStackTrace();
}catch(InputMismatchException e){
System.out.println("請輸入整數(shù)");
e.printStackTrace();
}catch(Exception e){ //位置應(yīng)該在最后一個catch塊中
//如果出現(xiàn)的異常沒有被前面的catch塊匹配果元,那么在此處進(jìn)行處理
System.out.println("出錯啦~~");
e.printStackTrace();
}finally{
System.out.println("=====運(yùn)算結(jié)束=====");
}
}
}
終止finally執(zhí)行的方法
上面的程序如果第二個輸入的數(shù)字為0的話,那么程序就終止了犀盟,沒有任何打印輸出而晒。因?yàn)楫?dāng)執(zhí)行System.exit(1)
方法就表示程序無條件終止運(yùn)行,后面的finally也就不會被執(zhí)行
return的說明
當(dāng)代碼中出現(xiàn)return時且蓬,一定是finally語句塊執(zhí)行完成后才會去執(zhí)行相應(yīng)的return代碼欣硼,無論return語句在什么位置题翰。
示例:
public class TryDemo {
public static void main(String[] args) {
int result=test();
System.out.println("one和two的商是:"+ result);
}
public static int test(){
Scanner input=new Scanner(System.in);
System.out.println("=====運(yùn)算開始=====");
try{
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
return one/two;
}catch(ArithmeticException e){
System.out.println("除數(shù)不允許為零");
return 0;
}finally{
System.out.println("=====運(yùn)算結(jié)束=====");
return -100000; //最好不要在finally語句中返回方法結(jié)果
}
}
}
運(yùn)行結(jié)果
當(dāng)輸入的兩個數(shù)為36和6時恶阴,程序的執(zhí)行結(jié)果為:one和two的商是:-10000
當(dāng)輸入的兩個數(shù)為36和0時,程序的執(zhí)行結(jié)果為:one和two的商是:-10000
所以當(dāng)try/catch
與finally
同時存在return
語句返回方法值時豹障,無論程序是否正常執(zhí)行冯事,最終返回的都是finally
的結(jié)果
throw和throws
可以通過throws
聲明將要拋出何種類型的異常,通過throw
將產(chǎn)生的異常拋出
throws
throws
語句用在方法定義時聲明該方法要拋出的異常類型
如果一個方法可能會出現(xiàn)異常血公,但不想或者沒有能力處理這種異常昵仅,可以在方法聲明處用throws
子句來聲明拋出異常
當(dāng)方法拋出異常時,方法將不對這些類型及其子類類型的異常作處理累魔,而拋向調(diào)用該方法的方法摔笤,由他去處理
使用規(guī)則
- 如果是不可查異常
(unchecked exception)
,即Error垦写、RuntimeException
或它們的子類吕世,那么可以不使用throws
關(guān)鍵字來聲明要拋出的異常,編譯仍能順利通過梯投,但在運(yùn)行時會被系統(tǒng)拋出 - 如果一個方法中可能出現(xiàn)可查異常命辖,要么用
try-catch
語句捕獲,要么用throws
子句聲明將它拋出分蓖,否則編譯出錯 - 當(dāng)拋出了檢查異常時尔艇,則該方法的調(diào)用者必須處理或者重新拋出該異常
- 當(dāng)子類重寫父類拋出異常的方法時,聲明的異常必須是父類方法所聲明異常的同類或子類
通過throws拋出異常的解決方案
1.throws
后面接多個異常類型么鹤,中間用逗號分隔
2.throws
后面接Exception
终娃,表示任何異常都向外拋出
注意事項(xiàng)
根據(jù)throws
拋出的異常類型來決定調(diào)用者是否必須處理該異常,如果是非檢查異常則可以不處理蒸甜,而如果是檢查異常尝抖,則必須捕獲處理毡们,而Exception
也包含了非檢查異常,所以也必須進(jìn)行處理昧辽。
對于方法只拋出非檢查異常我們可以通過文檔注釋來標(biāo)識方法拋出的異常衙熔,這樣對調(diào)用者更友好。
示例
import java.util.InputMismatchException;
import java.util.Scanner;
public class TryDemo {
public static void main(String[] args) {
//在Eclipse中可以選中代碼通過右鍵Surround With -> Try/Catch Block完成處理異常的代碼
//方案一:針對方法拋出的多個異常進(jìn)行捕獲處理,此時可以只選擇捕獲處理其中一個異常
try {
int result=test();
System.out.println("one和two的商是:"+ result);
} catch (ArithmeticException e) {
System.out.println("除數(shù)不允許為零");
e.printStackTrace();
} catch (InputMismatchException e){
System.out.println("請輸入整數(shù)");
e.printStackTrace();
}
//方案二:如果方法拋出的是Exception類型搅荞,那么catch塊中必須包含對Exception的處理红氯,其他子類異常處理是可選的
try{
int result = test2();
System.out.println("one和two的商是:" + result);
}catch(ArithmeticException e){
}catch(InputMismatchException e){
}catch(Exception e){//這個catch塊必須包含
}
}
//方案一:throws后面接多個異常類型,中間用逗號分隔
/**
* 使用文檔注釋給出異常提示
* @return
* @throws ArithmeticException
* @throws InputMismatchException
*/
public static int test() throws ArithmeticException, InputMismatchException{
Scanner input=new Scanner(System.in);
System.out.println("=====運(yùn)算開始=====");
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
System.out.println("=====運(yùn)算結(jié)束=====");
return one / two;
}
//方案二:throws后面接Exception咕痛,表示任何異常都向外拋出
public static int test2() throws Exception{
Scanner input=new Scanner(System.in);
System.out.println("=====運(yùn)算開始=====");
System.out.print("請輸入第一個整數(shù):");
int one=input.nextInt();
System.out.print("請輸入第二個整數(shù):");
int two=input.nextInt();
System.out.println("=====運(yùn)算結(jié)束=====");
return one / two;
}
}
throw
throw
用來拋出一個異常痢甘,例如:throw new IOException();
throw
拋出的只能夠是可拋出類Throwable
或者其子類的實(shí)例對象。如:throw new String("出錯了");
是錯誤的
throw拋出異常對象的處理方案
1.在throw
語句外面套上try-catch
塊茉贡,自己拋出的異常自己處理
2.通過throws
在方法聲明處拋出異常類型塞栅,誰調(diào)用方法誰處理,調(diào)用者可以自己處理腔丧,也可以繼續(xù)上拋放椰,此時可以拋出與throw
對象相同的類型或者其父類
異常拋出與處理作用
- 規(guī)避可能出現(xiàn)的風(fēng)險(xiǎn)
- 完成一些程序的邏輯
示例:完成酒店入住限定的場景
public class TryDemo {
public static void main(String[] args) {
try {
testAge();
}catch(Exception e){
//當(dāng)發(fā)生異常時打印:java.lang.Exception: 18歲以下愉粤,80歲以上的住客必須由親友陪同
e.printStackTrace();
}
}
// 描述酒店的入住規(guī)則:限定年齡砾医,18歲以下,80歲以上的住客必須由親友陪同
public static void testAge() {
try {
System.out.println("請輸入年齡:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if (age < 18 || age > 80) {
throw new Exception("18歲以下衣厘,80歲以上的住客必須由親友陪同");
} else {
System.out.println("歡迎入住本酒店");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
自定義異常
- 使用Java內(nèi)置的異常類可以描述在編程時出現(xiàn)的大部分異常情況如蚜。
- 也可以通過自定義異常描述特定業(yè)務(wù)產(chǎn)生的異常類型。
- 所謂自定義異常影暴,就是定義一個類错邦,去繼承
Throwable
類或者它的子類。
示例:我們用自定義異常去描述不滿足酒店入住規(guī)則的情況
/**
* 自定義異常
*/
public class HotelAgeException extends Exception {
public HotelAgeException(){
super("18歲以下型宙,80歲以上的住客必須由親友陪同");
}
}
//測試類
public class TryDemo {
public static void main(String[] args) {
try {
testAge();
} catch (HotelAgeException e) {
System.out.println(e.getMessage()); //18歲以下撬呢,80歲以上的住客必須由親友陪同
System.out.println("酒店前臺工作人員不允許辦理入住登記");
}catch(Exception e){
e.printStackTrace();
}
}
// 描述酒店的入住規(guī)則:限定年齡,18歲以下早歇,80歲以上的住客必須由親友陪同
public static void testAge() throws HotelAgeException {
System.out.println("請輸入年齡:");
Scanner input = new Scanner(System.in);
int age = input.nextInt();
if (age < 18 || age > 80) {
throw new HotelAgeException(); //拋出自定義異常
} else {
System.out.println("歡迎入住本酒店");
}
}
}
異常鏈
有時候我們會捕獲一個異常后再拋出另一個異常倾芝。此時異常會如何顯示呢?
示例:有三個方法testOne箭跳、testTwo晨另、testThree,依次進(jìn)行調(diào)用捕獲異常并拋出一個新的異常
public class TryDemo {
public static void main(String[] args) {
try {
testThree();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testOne() throws HotelAgeException {
throw new HotelAgeException();
}
public static void testTwo() throws Exception {
try {
testOne();
} catch (HotelAgeException e) {
throw new Exception("我是新產(chǎn)生的異常1");
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch (Exception e) {
throw new Exception("我是新產(chǎn)生的異常2");
}
}
}
運(yùn)行結(jié)果
只顯示了最后拋出的異常2谱姓,前面兩個方法的異常信息是丟失的借尿,這就是一種由于新拋出異常導(dǎo)致異常信息丟失的場景,如何將前面的異常信息也保留下來呢,java就提供了這種保留異常信息的機(jī)制路翻。
解決方案
- 可以通過構(gòu)造方法將異常對象作為參數(shù)去構(gòu)造新的異常對象
- 調(diào)用
Throwable
提供的initCause
方法
修改代碼:
public static void testTwo() throws Exception {
try {
testOne();
} catch (HotelAgeException e) {
//通過構(gòu)造方法保留異常信息
throw new Exception("我是新產(chǎn)生的異常1", e);
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch (Exception e) {
Exception e1=new Exception("我是新產(chǎn)生的異常2");
//通過initCause方法保留異常信息
e1.initCause(e);
throw e1;
// throw new Exception("我是新產(chǎn)生的異常2",e);
}
}
}
運(yùn)行結(jié)果:
異常鏈
當(dāng)捕獲一個異常后再拋出另一個異常時狈癞,如果希望將異常發(fā)生的原因一個傳一個串起來,即把底層的異常信息傳給上層,這樣逐層拋出而形成的一種鏈條
Java異常處理的優(yōu)點(diǎn)
- 把異常情況表現(xiàn)成異常類茂契,可以充分發(fā)揮類的可擴(kuò)展和可重用的優(yōu)勢
- 異常流程代碼和正常流程的代碼分離蝶桶,提高了程序的可讀性,簡化了程序的結(jié)構(gòu)
- 可以靈活的處理異常掉冶,有能力處理就捕獲并處理真竖,否則就拋出異常,由方法調(diào)用者處理它
經(jīng)驗(yàn)與總結(jié)
- 處理運(yùn)行時異常時厌小,采用邏輯去合理規(guī)避同時輔助
try-catch
處理 - 在多重
catch
塊后面,可以加一個catch(Exception)
來處理可能會被遺漏的異常 - 對于不確定的代碼恢共,也可以加上
try-catch
,處理潛在的異常 - 盡量去處理異常璧亚,切忌只是簡單的調(diào)用
printStackTrace()
去打印輸出 - 具體如何處理異常讨韭,要根據(jù)不同的業(yè)務(wù)需求和異常類型去決定
- 盡量添加
finally
語句塊去釋放占用的資源