1 異常介紹
1.1 異常概念
異常,就是不正常的意思。在生活中:醫(yī)生說,你的身體某個部位有異常,該部位和正常相比有點(diǎn)不同,該部位的功能將受影響。
異常 :在程序中的意思指的是程序在執(zhí)行過程中,出現(xiàn)的非正常的情況,最終會導(dǎo)致JVM的非正常停止。
在Java等面向?qū)ο蟮木幊陶Z言中,異常本身是一個類,產(chǎn)生異常就是創(chuàng)建異常對象并拋出了一個異常對象。Java處理異常的方式是中斷處理。
異常指的并不是語法錯誤,語法錯了,編譯不通過,不會產(chǎn)生字節(jié)碼文件,根本不能運(yùn)行.
1.2 異常體系
異常機(jī)制其實(shí)是幫助我們找到程序中的問題,異常的根類是java.lang.Throwable
,其下有兩個子類:java.lang.Error
與java.lang.Exception
,平常所說的異常指java.lang.Exception
。
[圖片上傳失敗...(image-d097cb-1578662565570)]
Throwable體系:
- Error:嚴(yán)重錯誤Error,無法通過處理的錯誤,只能事先避免,好比絕癥。
- Exception:表示異常,異常產(chǎn)生后程序員可以通過代碼的方式糾正,使程序繼續(xù)運(yùn)行,是必須要處理的。好比感冒、闌尾炎。
Throwable中的常用方法:
-
public void printStackTrace()
:打印異常的詳細(xì)信息。包含了異常的類型,異常的原因,還包括異常出現(xiàn)的位置,在開發(fā)和調(diào)試階段,都得使用printStackTrace。
-
public String getMessage()
:獲取發(fā)生異常的原因。提示給用戶的時候,就提示錯誤原因。
public String toString()
:獲取異常的類型和異常描述信息(不用)。
出現(xiàn)異常,不要緊張,把異常的簡單類名,拷貝到API中去查。
[圖片上傳失敗...(image-3dc2f8-1578662565570)]
1.3 異常分類
我們平常說的異常就是指Exception,因?yàn)檫@類異常一旦出現(xiàn),我們就要對代碼進(jìn)行更正,修復(fù)程序。
異常(Exception)的分類:根據(jù)在編譯時期還是運(yùn)行時期去檢查異常?
- 編譯時期異常:checked異常。在編譯時期,就會檢查,如果沒有處理異常,則編譯失敗。(如日期格式化異常)
- 運(yùn)行時期異常:runtime異常。在運(yùn)行時期,檢查異常.在編譯時期,運(yùn)行異常不會編譯器檢測(不報(bào)錯)。(如數(shù)學(xué)異常)
1.4 異常的產(chǎn)生過程解析
先運(yùn)行下面的程序,程序會產(chǎn)生一個數(shù)組索引越界異常ArrayIndexOfBoundsException。我們通過圖解來解析下異常產(chǎn)生的過程。
工具類
public class ArrayTools {
// 對給定的數(shù)組通過給定的角標(biāo)獲取元素。
public static int getElement(int[] arr, int index) {
int element = arr[index];
return element;
}
}
測試類
public class ExceptionDemo {
public static void main(String[] args) {
int[] arr = { 34, 12, 67 };
intnum = ArrayTools.getElement(arr, 4)
System.out.println("num=" + num);
System.out.println("over");
}
}
上述程序執(zhí)行過程圖解:
[圖片上傳失敗...(image-c5d5c5-1578662565570)]
2 異常的處理
Java異常處理的五個關(guān)鍵字:try、catch、finally、throw、throws
2.1 拋出異常throw
在編寫程序時,我們必須要考慮程序出現(xiàn)問題的情況。比如,在定義方法時,方法需要接受參數(shù)。那么,當(dāng)調(diào)用方法使用接受到的參數(shù)時,首先需要先對參數(shù)數(shù)據(jù)進(jìn)行合法的判斷,數(shù)據(jù)若不合法,就應(yīng)該告訴調(diào)用者,傳遞合法的數(shù)據(jù)進(jìn)來。這時需要使用拋出異常的方式來告訴調(diào)用者。
在java中,提供了一個throw關(guān)鍵字,它用來拋出一個指定的異常對象。那么,拋出一個異常具體如何操作呢?
創(chuàng)建一個異常對象。封裝一些提示信息(信息可以自己編寫)。
-
需要將這個異常對象告知給調(diào)用者。怎么告知呢?怎么將這個異常對象傳遞到調(diào)用者處呢?通過關(guān)鍵字throw就可以完成。throw 異常對象。
throw用在方法內(nèi),用來拋出一個異常對象,將這個異常對象傳遞到調(diào)用者處,并結(jié)束當(dāng)前方法的執(zhí)行。
使用格式:
throw new 異常類名(參數(shù));
例如:
throw new NullPointerException("要訪問的arr數(shù)組不存在");
throw new ArrayIndexOutOfBoundsException("該索引在數(shù)組中不存在,已超出范圍");
學(xué)習(xí)完拋出異常的格式后,我們通過下面程序演示下throw的使用。
public class ThrowDemo {
public static void main(String[] args) {
//創(chuàng)建一個數(shù)組
int[] arr = {2,4,52,2};
//根據(jù)索引找對應(yīng)的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
* 根據(jù) 索引找到數(shù)組中對應(yīng)的元素
*/
public static int getElement(int[] arr,int index){
//判斷 索引是否越界
if(index<0 || index>arr.length-1){
/*
判斷條件如果滿足,當(dāng)執(zhí)行完throw拋出異常對象后,方法已經(jīng)無法繼續(xù)運(yùn)算。
這時就會結(jié)束當(dāng)前方法的執(zhí)行,并將異常告知給調(diào)用者。這時就需要通過異常來解決。
*/
throw new ArrayIndexOutOfBoundsException("哥們,角標(biāo)越界了~~~");
}
int element = arr[index];
return element;
}
}
注意:如果產(chǎn)生了問題,我們就會throw將問題描述類即異常進(jìn)行拋出,也就是將問題返回給該方法的調(diào)用者。
那么對于調(diào)用者來說,該怎么處理呢?一種是進(jìn)行捕獲處理,另一種就是繼續(xù)講問題聲明出去,使用throws聲明處理。
2.2 Objects非空判斷
還記得我們學(xué)習(xí)過一個類Objects嗎,曾經(jīng)提到過它由一些靜態(tài)的實(shí)用方法組成,這些方法是null-save(空指針安全的)或null-tolerant(容忍空指針的),那么在它的源碼中,對對象為null的值進(jìn)行了拋出異常操作。
-
public static <T> T requireNonNull(T obj)
:查看指定引用對象不是null。
查看源碼發(fā)現(xiàn)這里對為null的進(jìn)行了拋出異常操作:
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
2.3 聲明異常throws
聲明異常:將問題標(biāo)識出來,報(bào)告給調(diào)用者。如果方法內(nèi)通過throw拋出了編譯時異常,而沒有捕獲處理(稍后講解該方式),那么必須通過throws進(jìn)行聲明,讓調(diào)用者去處理。
關(guān)鍵字throws運(yùn)用于方法聲明之上,用于表示當(dāng)前方法不處理異常,而是提醒該方法的調(diào)用者來處理異常(拋出異常).
聲明異常格式:
修飾符 返回值類型 方法名(參數(shù)) throws 異常類名1,異常類名2…{ }
聲明異常的代碼演示:
public class ThrowsDemo {
public static void main(String[] args) throws FileNotFoundException {
read("a.txt");
}
// 如果定義功能時有問題發(fā)生需要報(bào)告給調(diào)用者。可以通過在方法上使用throws關(guān)鍵字進(jìn)行聲明
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt這個文件
// 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個錯誤 也就是異常 throw
throw new FileNotFoundException("文件不存在");
}
}
}
throws用于進(jìn)行異常類的聲明,若該方法可能有多種異常情況產(chǎn)生,那么在throws后面可以寫多個異常類,用逗號隔開。
public class ThrowsDemo2 {
public static void main(String[] args) throws IOException {
read("a.txt");
}
public static void read(String path)throws FileNotFoundException, IOException {
if (!path.equals("a.txt")) {//如果不是 a.txt這個文件
// 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個錯誤 也就是異常 throw
throw new FileNotFoundException("文件不存在");
}
if (!path.equals("b.txt")) {
throw new IOException();
}
}
}
2.4 捕獲異常try…catch
如果異常出現(xiàn)的話,會立刻終止程序,所以我們得處理異常:
- 該方法不處理,而是聲明拋出,由該方法的調(diào)用者來處理(throws)。
- 在方法中使用try-catch的語句塊來處理異常。
try-catch的方式就是捕獲異常。
- 捕獲異常:Java中對異常有針對性的語句進(jìn)行捕獲,可以對出現(xiàn)的異常進(jìn)行指定方式的處理。
捕獲異常語法如下:
try{
編寫可能會出現(xiàn)異常的代碼
}catch(異常類型 e){
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}
try:該代碼塊中編寫可能產(chǎn)生異常的代碼。
catch:用來進(jìn)行某種異常的捕獲,實(shí)現(xiàn)對捕獲到的異常進(jìn)行處理。
注意:try和catch都不能單獨(dú)使用,必須連用。
演示如下:
public class TryCatchDemo {
public static void main(String[] args) {
try {// 當(dāng)產(chǎn)生異常時,必須有處理方式。要么捕獲,要么聲明。
read("b.txt");
} catch (FileNotFoundException e) {// 括號中需要定義什么呢?
//try中拋出的是什么異常,在括號中就定義什么異常類型
System.out.println(e);
}
System.out.println("over");
}
/*
*
* 我們 當(dāng)前的這個方法中 有異常 有編譯期異常
*/
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt這個文件
// 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個錯誤 也就是異常 throw
throw new FileNotFoundException("文件不存在");
}
}
}
如何獲取異常信息:
Throwable類中定義了一些查看方法:
-
public String getMessage()
:獲取異常的描述信息,原因(提示給用戶的時候,就提示錯誤原因。
-
public String toString()
:獲取異常的類型和異常描述信息(不用)。 -
public void printStackTrace()
:打印異常的跟蹤棧信息并輸出到控制臺。
包含了異常的類型,異常的原因,還包括異常出現(xiàn)的位置,在開發(fā)和調(diào)試階段,都得使用printStackTrace。
2.4 finally 代碼塊
finally:有一些特定的代碼無論異常是否發(fā)生,都需要執(zhí)行。另外,因?yàn)楫惓l(fā)程序跳轉(zhuǎn),導(dǎo)致有些語句執(zhí)行不到。而finally就是解決這個問題的,在finally代碼塊中存放的代碼都是一定會被執(zhí)行的。
什么時候的代碼必須最終執(zhí)行?
當(dāng)我們在try語句塊中打開了一些物理資源(磁盤文件/網(wǎng)絡(luò)連接/數(shù)據(jù)庫連接等),我們都得在使用完之后,最終關(guān)閉打開的資源。
finally的語法:
try...catch....finally:自身需要處理異常,最終還得關(guān)閉資源。
注意:finally不能單獨(dú)使用。
比如在我們之后學(xué)習(xí)的IO流中,當(dāng)打開了一個關(guān)聯(lián)文件的資源,最后程序不管結(jié)果如何,都需要把這個資源關(guān)閉掉。
finally代碼參考如下:
public class TryCatchDemo4 {
public static void main(String[] args) {
try {
read("a.txt");
} catch (FileNotFoundException e) {
//抓取到的是編譯期異常 拋出去的是運(yùn)行期
throw new RuntimeException(e);
} finally {
System.out.println("不管程序怎樣,這里都將會被執(zhí)行。");
}
System.out.println("over");
}
/*
*
* 我們 當(dāng)前的這個方法中 有異常 有編譯期異常
*/
public static void read(String path) throws FileNotFoundException {
if (!path.equals("a.txt")) {//如果不是 a.txt這個文件
// 我假設(shè) 如果不是 a.txt 認(rèn)為 該文件不存在 是一個錯誤 也就是異常 throw
throw new FileNotFoundException("文件不存在");
}
}
}
當(dāng)只有在try或者catch中調(diào)用退出JVM的相關(guān)方法,此時finally才不會執(zhí)行,否則finally永遠(yuǎn)會執(zhí)行。
[圖片上傳失敗...(image-63a90d-1578662565570)]
2.5 異常注意事項(xiàng)
-
多個異常使用捕獲又該如何處理呢?
- 多個異常分別處理。
- 多個異常一次捕獲,多次處理。
- 多個異常一次捕獲一次處理。
一般我們是使用一次捕獲多次處理方式,格式如下:
try{ 編寫可能會出現(xiàn)異常的代碼 }catch(異常類型A e){ 當(dāng)try中出現(xiàn)A類型異常,就用該catch來捕獲. 處理異常的代碼 //記錄日志/打印異常信息/繼續(xù)拋出異常 }catch(異常類型B e){ 當(dāng)try中出現(xiàn)B類型異常,就用該catch來捕獲. 處理異常的代碼 //記錄日志/打印異常信息/繼續(xù)拋出異常 }
注意:這種異常處理方式,要求多個catch中的異常不能相同,并且若catch中的多個異常之間有子父類異常的關(guān)系,那么子類異常要求在上面的catch處理,父類異常在下面的catch處理。
運(yùn)行時異常被拋出可以不處理。即不捕獲也不聲明拋出。
如果finally有return語句,永遠(yuǎn)返回finally中的結(jié)果,避免該情況.
如果父類拋出了多個異常,子類重寫父類方法時,拋出和父類相同的異常或者是父類異常的子類或者不拋出異常。
父類方法沒有拋出異常,子類重寫父類該方法時也不可拋出異常。此時子類產(chǎn)生該異常,只能捕獲處理,不能聲明拋出
3 自定義異常
3.1 概述
為什么需要自定義異常類:
我們說了Java中不同的異常類,分別表示著某一種具體的異常情況,那么在開發(fā)中總是有些異常情況是SUN沒有定義好的,此時我們根據(jù)自己業(yè)務(wù)的異常情況來定義異常類。例如年齡負(fù)數(shù)問題,考試成績負(fù)數(shù)問題等等。
在上述代碼中,發(fā)現(xiàn)這些異常都是JDK內(nèi)部定義好的,但是實(shí)際開發(fā)中也會出現(xiàn)很多異常,這些異常很可能在JDK中沒有定義過,例如年齡負(fù)數(shù)問題,考試成績負(fù)數(shù)問題.那么能不能自己定義異常呢?
什么是自定義異常類:
在開發(fā)中根據(jù)自己業(yè)務(wù)的異常情況來定義異常類.
自定義一個業(yè)務(wù)邏輯異常: RegisterException。一個注冊異常類。
異常類如何定義:
- 自定義一個編譯期異常: 自定義類 并繼承于
java.lang.Exception
。 - 自定義一個運(yùn)行時期的異常類:自定義類 并繼承于
java.lang.RuntimeException
。
3.2 自定義異常示例
要求:我們模擬注冊操作,如果用戶名已存在,則拋出異常并提示:親,該用戶名已經(jīng)被注冊。
首先定義一個登陸異常類RegisterException:
// 業(yè)務(wù)邏輯異常
public class RegisterException extends Exception {
/**
* 空參構(gòu)造
*/
public RegisterException() {
}
/**
*
* @param message 表示異常提示
*/
public RegisterException(String message) {
super(message);
}
}
模擬登陸操作,使用數(shù)組模擬數(shù)據(jù)庫中存儲的數(shù)據(jù),并提供當(dāng)前注冊賬號是否存在方法用于判斷。
public class Demo {
// 模擬數(shù)據(jù)庫中已存在賬號
private static String[] names = {"bill","hill","jill"};
public static void main(String[] args) {
//調(diào)用方法
try{
// 可能出現(xiàn)異常的代碼
checkUsername("nill");
System.out.println("注冊成功");//如果沒有異常就是注冊成功
}catch(RegisterException e){
//處理異常
e.printStackTrace();
}
}
//判斷當(dāng)前注冊賬號是否存在
//因?yàn)槭蔷幾g期異常,又想調(diào)用者去處理 所以聲明該異常
public static boolean checkUsername(String uname) throws LoginException{
for (String name : names) {
if(name.equals(uname)){//如果名字在這里面 就拋出登陸異常
throw new RegisterException("親"+name+"已經(jīng)被注冊了!");
}
}
return true;
}
}