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

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

從上面的結(jié)果中可以看出,JVM會(huì)先進(jìn)入到catch代碼塊中進(jìn)行異常的正常處理,如果發(fā)現(xiàn)在catch中也產(chǎn)生了異常,則會(huì)進(jìn)入到finally中執(zhí)行,finally執(zhí)行完畢后,會(huì)再把catch中的異常拋出。所以即使我們?cè)趖ry或catch中拋出了異常,也并不會(huì)影響finally的執(zhí)行。
2.4 異常屏蔽
但是這時(shí)有的小伙伴又提問(wèn)了,如果我們?cè)趫?zhí)行finally語(yǔ)句中也拋出一個(gè)異常,又會(huì)怎么樣呢?所以小編繼續(xù)給大家進(jìn)行論證,我們對(duì)上面的案例進(jìn)行適當(dāng)改造,在finally中也拋出一個(gè)異常。
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中拋出一個(gè)新的運(yùn)行時(shí)異常
throw new RuntimeException(e);
} finally {
System.out.println("非特殊情況,一定會(huì)執(zhí)行finally里的代碼");
//在finally中也拋出一個(gè)異常
throw new IllegalArgumentException("非法參數(shù)異常");
}
}我們還是直接來(lái)看看執(zhí)行結(jié)果:

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

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

