詳解Java拋出和聲明異常的代碼實(shí)現(xiàn)
一. 拋出和聲明異常
1. 概述
我們在編寫代碼時,有時候因?yàn)槟承┰?,并不想在這個方法中立即處理產(chǎn)生的異常,也就是說并不想進(jìn)行異常的捕獲。那么此時我們可以把這些異常先進(jìn)行聲明和拋出,即在這個方法中暫時不處理,而是扔給別人去處理。就好比你發(fā)現(xiàn)有個小孩犯了錯,但你不是這個小孩的監(jiān)護(hù)人,你來批評處理這個小孩子就不太合適,所以可以把他抓住扔給其父母,讓他的父母來處理這個錯誤。
這就是所謂的異常傳播機(jī)制:當(dāng)某個方法拋出了異常,如果當(dāng)前方法沒有捕獲該異常,該異常就會被拋到更上層的調(diào)用方法,逐層傳遞,直到遇到某個 try ... catch 被捕獲為止。
異常的傳播,在Java中主要是用聲明和拋出異常的關(guān)鍵字來實(shí)現(xiàn),分別是throws和throw。我們可以使用throws關(guān)鍵字在方法上聲明本方法要拋出的異常,使用throw關(guān)鍵字拋出某個異常對象。接下來就給大家詳細(xì)介紹該如何聲明異常和拋出異常。
2. throw拋出異常
2.1 基本語法
當(dāng)代碼中發(fā)生了異常,程序會自動拋出一個異常對象,該對象包含了異常的類型和相關(guān)信息。但是如果我們想主動拋出異常,則可以使用throw關(guān)鍵字來實(shí)現(xiàn)。
throw 某個Exception類;
這里的某個Exception類必須是Throwable類或其子類對象。如果是自定義的異常類,也必須是Throwable的直接或間接子類,否則會發(fā)生錯誤。
2.2 代碼實(shí)現(xiàn)
接下來給大家設(shè)計(jì)一個案例,來演示throw的使用:
public class Demo04 {
// throw的使用
public static void myMethod(boolean flag) throws Exception {
if (flag) {
//當(dāng)flag為true時就拋出一個Exception對象
throw new Exception("主動拋出來的異常對象");
}
}
public static void caller() {
try {
//調(diào)用myMethod方法
myMethod(true);
} catch (Exception e) {
e.printStackTrace();
System.out.println("處理主動拋出來的異常:" + e.getMessage());
}
}
public static void main(String[] args) {
caller();
}
}在上面這個案例中,myMethod方法產(chǎn)生了異常,但是自己卻沒有處理,而是進(jìn)行了拋出。那么會拋給誰呢?我們看到,此時caller方法調(diào)用了myMethod方法,即caller方法是myMethod方法的上層調(diào)用者,所以myMethod方法中產(chǎn)生的異常就拋給了caller方法。但是如果在caller方法中對這個異常也沒有進(jìn)行處理,caller方法也會把這個異常繼續(xù)向上層傳遞。這樣逐層向上拋出異常,直到最外層的異常處理程序終止程序并打印出調(diào)用棧才會結(jié)束。我們來看看異常信息棧的打印結(jié)果:

從上面的異常棧信息中可以看出,Exception是在myMethod方法中被拋出的,從下往上看,調(diào)用層次依次是:
- main()調(diào)用caller();
- caller()調(diào)用myMethod();
- myMethod()拋出異常;
而且每層的調(diào)用都給出了源代碼的行號,我們可以直接定位到產(chǎn)生異常的代碼行,這樣我們就可以根據(jù)異常信息棧進(jìn)行異常調(diào)試。尤其是要注意,如果異常信息棧中有“Caused by: Xxx”這樣的信息,說明我們捕獲到了造成問題的根源。但是有時候異常信息中可能并沒有“Caused by”這樣的信息,我們可以在代碼中使用Throwable類的getCause()方法來獲取原始異常,此時如果返回了null,說明已經(jīng)是“根異常”了。這樣有了完整的異常棧的信息,我們才能快速定位并修復(fù)代碼的問題。另外我們還要注意,throw關(guān)鍵字不會單獨(dú)使用,它的使用要符合異常的處理機(jī)制。我們一般不會主動拋出一個新的異常對象,而是應(yīng)該避免異常的產(chǎn)生。
2.3 在try或catch中拋出異常
有些同學(xué)很善于思考,于是就提出了問題:如果我們在 try 或者 catch 語句塊中拋出一個異常,那么 finally 語句還能不能執(zhí)行? 為了給大家解答清楚這個問題,再給大家設(shè)計(jì)一個案例。
public static void main(String[] args) {
try {
int a=100;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("執(zhí)行catch代碼,異常信息:"+e.getMessage());
//在catch中拋出一個新的運(yùn)行時異常
throw new RuntimeException(e);
} finally {
System.out.println("非特殊情況,一定會執(zhí)行finally里的代碼");
}
}我們直接來看上述代碼的執(zhí)行結(jié)果:

從上面的結(jié)果中可以看出,JVM會先進(jìn)入到catch代碼塊中進(jìn)行異常的正常處理,如果發(fā)現(xiàn)在catch中也產(chǎn)生了異常,則會進(jìn)入到finally中執(zhí)行,finally執(zhí)行完畢后,會再把catch中的異常拋出。所以即使我們在try或catch中拋出了異常,也并不會影響finally的執(zhí)行。
2.4 異常屏蔽
但是這時有的小伙伴又提問了,如果我們在執(zhí)行finally語句中也拋出一個異常,又會怎么樣呢?所以小編繼續(xù)給大家進(jìn)行論證,我們對上面的案例進(jìn)行適當(dāng)改造,在finally中也拋出一個異常。
public static void main(String[] args) {
try {
int a=100/0;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("執(zhí)行catch代碼,異常信息:"+e.getMessage());
//在catch中拋出一個新的運(yùn)行時異常
throw new RuntimeException(e);
} finally {
System.out.println("非特殊情況,一定會執(zhí)行finally里的代碼");
//在finally中也拋出一個異常
throw new IllegalArgumentException("非法參數(shù)異常");
}
}我們還是直接來看看執(zhí)行結(jié)果:

從上面的結(jié)果中我們可以看出,在finally拋出異常后,原來catch中拋出的異常不見了。這是因?yàn)槟J(rèn)情況下只能拋出一個異常,而之前那個沒被拋出的異常稱為“被屏蔽的異常(Suppressed Exception) ”。
2.5 獲取全部異常信息
但是有些較真的小伙伴就說,如果我就想知道所有的異常信息,包括catch中被屏蔽的那些異常信息,這該怎么辦?
我們可以定義一個Exception的origin變量來保存原始的異常信息,然后調(diào)用 Throwable對象中的addSuppressed()方法 ,把原始的異常信息添加進(jìn)來,最后在 finally中繼續(xù) 拋出,并 利用throws關(guān)鍵字拋出Exception 。
public class Demo07 {
//這里要利用throws關(guān)鍵字拋出Exception
@SuppressWarnings("finally")
public static void main(String[] args) throws Exception {
//定義一個異常變量,存儲catch中的異常信息
Exception origin = null;
try {
int a=100/0;
System.out.println("a="+a);
} catch (Exception e) {
System.out.println("執(zhí)行catch代碼,異常信息:"+e.getMessage());
//存儲catch中的異常信息
origin = e;
//拋出catch中的異常信息
throw e;
} finally {
System.out.println("非特殊情況,一定會執(zhí)行finally里的代碼");
Exception e = new IllegalArgumentException();
if (origin != null) {
//將catch中的異常信息添加到finally中的異常信息中
e.addSuppressed(origin);
}
//拋出finally中的異常信息,注意此時需要在方法中利用throws關(guān)鍵字拋出Exception
throw e;
}
}
}此時的執(zhí)行結(jié)果如下所示:

從上面的結(jié)果中我們可以看出,即使catch和finally都拋出了異常時,catch異常被屏蔽,但我們通過addSuppressed方法,最終仍然獲取到了完整的異常信息。但是我們也要知道,絕大多數(shù)情況下,都不應(yīng)該在finally中拋出異常。
3. throws聲明異常
對于方法中不想處理的異常,除了可以利用throw關(guān)鍵字進(jìn)行拋出之外,還有別的辦法嗎?其實(shí)我們還可以在該方法的頭部,利用throws關(guān)鍵字來聲明這個不想處理的異常,把該異常傳遞到方法的外部進(jìn)行處理。
3.1 基本語法
throws關(guān)鍵字的基本語法如下:
返回值類型 methodName(參數(shù)列表) throws Exception 1,Exception2,…{…}通過這個語法,我們可以看出,在一個方法中可以利用throws關(guān)鍵字同時聲明拋出多個異常:Exception 1,Exception2,… 多個異常之間利用","逗號分隔。如果被調(diào)用方法拋出的異常類型在這個異常列表中,則必須在該方法中捕獲或繼續(xù)向上層調(diào)用者拋出異常。而這里繼續(xù)聲明拋出的異常,可以是方法本身產(chǎn)生的異常,也可以是調(diào)用的其他方法拋出的異常。
3.2 代碼實(shí)現(xiàn)
為了讓大家理解throws關(guān)鍵字的用法,接下來繼續(xù)設(shè)計(jì)一個案例進(jìn)行說明。
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
/**
* @author 一一哥Sun
*/
public class Demo08 {
public static void main(String[] args) {
try {
//在調(diào)用readFile的上層方法中進(jìn)行異常的捕獲
readFile();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// throws的用法--拋出了兩個異常
public static void readFile() throws FileNotFoundException,IOException {
// 定義一個緩沖流對象,以后在IO流階段會細(xì)講
BufferedReader reader = null;
// 對接一個file.txt文件,該文件可能不存在
reader = new BufferedReader(new FileReader("file.txt"));
// 讀取文件中的內(nèi)容。所有的IO流都可能會產(chǎn)生IO流異常
String line = reader.readLine();
while (line != null) {
System.out.println(line);
line = reader.readLine();
}
}
}在上面的案例中,我們在readFile方法中進(jìn)行了IO流的操作,此時遇到了兩個異常,但是我們不想在這里進(jìn)行異常的捕獲,就可以利用throws關(guān)鍵字進(jìn)行異常的聲明拋出。然后main()方法作為調(diào)用readFile的上層方法,就需要對異常進(jìn)行捕獲。當(dāng)然,如果main方法也不捕獲這兩個異常,該異常就會繼續(xù)向上拋,拋給JVM虛擬機(jī),由虛擬機(jī)進(jìn)行處理。
但是我們在使用throws時要注意,子類在重寫父類的方法時,如果父類的方法帶有throws聲明,子類方法聲明中的 throws異常,不能出現(xiàn)父類對應(yīng)方法中throws里沒有的異常類型,即子類方法拋出的異常范圍不能超過父類定義的范圍。也就是說,子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或同類,子類方法聲明拋出的異常不能比父類方法聲明拋出的異常多。
因此利用這一特性,throws也可以用來限制子類的行為。子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多。
3.3 throws執(zhí)行邏輯
根據(jù)上面的案例執(zhí)行結(jié)果,給大家總結(jié)一下throws關(guān)鍵字聲明拋出異常的執(zhí)行邏輯是:
- 如果當(dāng)前方法不知道如何處理某些異常,該異??梢越挥筛弦患壍恼{(diào)用者來處理,比如main()方法;
- 如果main()方法不知道該如何處理該異常,也可以使用throws關(guān)鍵字繼續(xù)聲明拋出,該異常將交給JVM去處理;
- 最終JVM會打印出異常的跟蹤棧信息,并中止程序運(yùn)行,這也是程序在遇到異常后自動結(jié)束的原因。
3.4 注意事項(xiàng)
我們在使用throws時也要注意如下這些事項(xiàng):
- 只能在方法的定義簽名處聲明可能拋出的異常類型,否則編譯器會報(bào)錯;
- 如果一個方法聲明了拋出異常,但卻沒有在上層的方法體中對拋出的異常進(jìn)行處理或繼續(xù)拋出該異常,編譯器會報(bào)錯;
- throws關(guān)鍵字只是聲明方法可能拋出的異常類型,它并不一定真的會拋出異常;
- 如果一個方法中可能會有多個異常拋出,可以使用逗號將它們分隔,如throws Exception1, Exception2, Exception3等;
- 子類方法拋出的異常范圍不能超過父類定義的范圍。
4. throw與throws的區(qū)別
由于throw和throws長得特別像,功能也有類似之處,為了不讓大家產(chǎn)生迷惑,所以給大家總結(jié)一下throw和throws的區(qū)別:
- throw關(guān)鍵字用來拋出一個特定的異常對象,可以使用throw關(guān)鍵字手動拋出異常,執(zhí)行throw一定會拋出某種異常對象;
- throws關(guān)鍵字用于聲明 一個方法可能拋出的所有異常信息,表示出現(xiàn)異常的一種可能性,但并不一定會發(fā)生這些異常 ;
- throw需要用戶自己捕獲相關(guān)的異常,再對其進(jìn)行相關(guān)包裝,最后將包裝后的異常信息拋出;
- throws通常不必顯示地捕獲異常,可以由系統(tǒng)自動將所有捕獲的異常信息拋給上層方法;
- 我們通常在方法或類定義時,通過throws關(guān)鍵字聲明該方法或類可能拋出的異常信息,而在方法或類的內(nèi)部通過throw關(guān)鍵字聲明一個具體的異常信息。
二. 結(jié)語
至此,就把今天的內(nèi)容講解完畢了,但是異常的學(xué)習(xí)還沒結(jié)束哦。下一篇文章中,會繼續(xù)帶大家學(xué)習(xí)異常中的新特性,敬請期待哦。
以上就是詳解Java拋出和聲明異常的代碼實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java拋出和聲明異常的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 線程池詳解及創(chuàng)建簡單實(shí)例
這篇文章主要介紹了Java 線程池詳解及創(chuàng)建簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
SpringBoot整合Flyway實(shí)現(xiàn)數(shù)據(jù)庫的初始化和版本管理操作
Flyway?是一款開源的數(shù)據(jù)庫版本管理工具,它可以很方便的在命令行中使用,或者在Java應(yīng)用程序中引入,用于管理我們的數(shù)據(jù)庫版本,這篇文章主要介紹了SpringBoot整合Flyway實(shí)現(xiàn)數(shù)據(jù)庫的初始化和版本管理,需要的朋友可以參考下2023-06-06
基于java ssm springboot+mybatis酒莊內(nèi)部管理系統(tǒng)設(shè)計(jì)和實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了java ssm springboot+mybatis實(shí)現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
Spring boot 路徑映射的實(shí)現(xiàn)
這篇文章主要介紹了spring boot 路徑映射的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
springboot + swagger 實(shí)例代碼
本篇文章主要介紹了springboot + swagger 實(shí)例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
java調(diào)用WebService服務(wù)的四種方法總結(jié)
WebService是一種跨編程語言、跨操作系統(tǒng)平臺的遠(yuǎn)程調(diào)用技術(shù),已存在很多年了,很多接口也都是通過WebService方式來發(fā)布的,下面這篇文章主要給大家介紹了關(guān)于java調(diào)用WebService服務(wù)的四種方法,需要的朋友可以參考下2021-11-11
Spring boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫(推薦)
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套JPA應(yīng)用框架,可使開發(fā)者用極簡的代碼即可實(shí)現(xiàn)對數(shù)據(jù)的訪問和操作。這篇文章主要介紹了Spring-boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫,需要的朋友可以參考下2018-05-05
SpringBoot通過參數(shù)注解自動獲取當(dāng)前用戶信息的方法
這篇文章主要介紹了SpringBoot通過參數(shù)注解自動獲取當(dāng)前用戶信息的方法,文中使用HandlerMethodArgumentResolver 類來實(shí)現(xiàn)這個功能,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-03-03
IDEA創(chuàng)建Maven一直爆紅無法下載的問題解決辦法
這篇文章主要介紹了關(guān)于IDEA創(chuàng)建Maven一直爆紅無法下載的問題的解決辦法,文中圖文結(jié)合的方式給大家講解的非常詳細(xì),對大家解決辦法非常有用,需要的朋友可以參考下2024-06-06

