詳解Java拋出和聲明異常的代碼實(shí)現(xiàn)
一. 拋出和聲明異常
1. 概述
我們?cè)诰帉懘a時(shí),有時(shí)候因?yàn)槟承┰?,并不想在這個(gè)方法中立即處理產(chǎn)生的異常,也就是說并不想進(jìn)行異常的捕獲。那么此時(shí)我們可以把這些異常先進(jìn)行聲明和拋出,即在這個(gè)方法中暫時(shí)不處理,而是扔給別人去處理。就好比你發(fā)現(xiàn)有個(gè)小孩犯了錯(cuò),但你不是這個(gè)小孩的監(jiān)護(hù)人,你來批評(píng)處理這個(gè)小孩子就不太合適,所以可以把他抓住扔給其父母,讓他的父母來處理這個(gè)錯(cuò)誤。
這就是所謂的異常傳播機(jī)制:當(dāng)某個(gè)方法拋出了異常,如果當(dāng)前方法沒有捕獲該異常,該異常就會(huì)被拋到更上層的調(diào)用方法,逐層傳遞,直到遇到某個(gè) try ... catch 被捕獲為止。
異常的傳播,在Java中主要是用聲明和拋出異常的關(guān)鍵字來實(shí)現(xiàn),分別是throws和throw。我們可以使用throws關(guān)鍵字在方法上聲明本方法要拋出的異常,使用throw關(guān)鍵字拋出某個(gè)異常對(duì)象。接下來就給大家詳細(xì)介紹該如何聲明異常和拋出異常。
2. throw拋出異常
2.1 基本語法
當(dāng)代碼中發(fā)生了異常,程序會(huì)自動(dòng)拋出一個(gè)異常對(duì)象,該對(duì)象包含了異常的類型和相關(guān)信息。但是如果我們想主動(dòng)拋出異常,則可以使用throw關(guān)鍵字來實(shí)現(xiàn)。
throw 某個(gè)Exception類;
這里的某個(gè)Exception類必須是Throwable類或其子類對(duì)象。如果是自定義的異常類,也必須是Throwable的直接或間接子類,否則會(huì)發(fā)生錯(cuò)誤。
2.2 代碼實(shí)現(xiàn)
接下來給大家設(shè)計(jì)一個(gè)案例,來演示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)拋出來的異常對(duì)象"); } } public static void caller() { try { //調(diào)用myMethod方法 myMethod(true); } catch (Exception e) { e.printStackTrace(); System.out.println("處理主動(dòng)拋出來的異常:" + e.getMessage()); } } public static void main(String[] args) { caller(); } }
在上面這個(gè)案例中,myMethod方法產(chǎn)生了異常,但是自己卻沒有處理,而是進(jìn)行了拋出。那么會(huì)拋給誰呢?我們看到,此時(shí)caller方法調(diào)用了myMethod方法,即caller方法是myMethod方法的上層調(diào)用者,所以myMethod方法中產(chǎn)生的異常就拋給了caller方法。但是如果在caller方法中對(duì)這個(gè)異常也沒有進(jìn)行處理,caller方法也會(huì)把這個(gè)異常繼續(xù)向上層傳遞。這樣逐層向上拋出異常,直到最外層的異常處理程序終止程序并打印出調(diào)用棧才會(huì)結(jié)束。我們來看看異常信息棧的打印結(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”這樣的信息,說明我們捕獲到了造成問題的根源。但是有時(shí)候異常信息中可能并沒有“Caused by”這樣的信息,我們可以在代碼中使用Throwable類的getCause()方法來獲取原始異常,此時(shí)如果返回了null,說明已經(jīng)是“根異常”了。這樣有了完整的異常棧的信息,我們才能快速定位并修復(fù)代碼的問題。另外我們還要注意,throw關(guān)鍵字不會(huì)單獨(dú)使用,它的使用要符合異常的處理機(jī)制。我們一般不會(huì)主動(dòng)拋出一個(gè)新的異常對(duì)象,而是應(yīng)該避免異常的產(chǎn)生。
2.3 在try或catch中拋出異常
有些同學(xué)很善于思考,于是就提出了問題:如果我們?cè)?/strong> try 或者 catch 語句塊中拋出一個(gè)異常,那么 finally 語句還能不能執(zhí)行? 為了給大家解答清楚這個(gè)問題,再給大家設(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里的代碼"); } }
我們直接來看上述代碼的執(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í)有的小伙伴又提問了,如果我們?cè)趫?zhí)行finally語句中也拋出一個(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ù)異常"); } }
我們還是直接來看看執(zhí)行結(jié)果:
從上面的結(jié)果中我們可以看出,在finally拋出異常后,原來catch中拋出的異常不見了。這是因?yàn)槟J(rèn)情況下只能拋出一個(gè)異常,而之前那個(gè)沒被拋出的異常稱為“被屏蔽的異常(Suppressed Exception) ”。
2.5 獲取全部異常信息
但是有些較真的小伙伴就說,如果我就想知道所有的異常信息,包括catch中被屏蔽的那些異常信息,這該怎么辦?
我們可以定義一個(gè)Exception的origin變量來保存原始的異常信息,然后調(diào)用 Throwable對(duì)象中的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 { //定義一個(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異常被屏蔽,但我們通過addSuppressed方法,最終仍然獲取到了完整的異常信息。但是我們也要知道,絕大多數(shù)情況下,都不應(yīng)該在finally中拋出異常。
3. throws聲明異常
對(duì)于方法中不想處理的異常,除了可以利用throw關(guān)鍵字進(jìn)行拋出之外,還有別的辦法嗎?其實(shí)我們還可以在該方法的頭部,利用throws關(guān)鍵字來聲明這個(gè)不想處理的異常,把該異常傳遞到方法的外部進(jìn)行處理。
3.1 基本語法
throws關(guān)鍵字的基本語法如下:
返回值類型 methodName(參數(shù)列表) throws Exception 1,Exception2,…{…}
通過這個(gè)語法,我們可以看出,在一個(gè)方法中可以利用throws關(guān)鍵字同時(shí)聲明拋出多個(gè)異常:Exception 1,Exception2,… 多個(gè)異常之間利用","逗號(hào)分隔。如果被調(diào)用方法拋出的異常類型在這個(gè)異常列表中,則必須在該方法中捕獲或繼續(xù)向上層調(diào)用者拋出異常。而這里繼續(xù)聲明拋出的異常,可以是方法本身產(chǎn)生的異常,也可以是調(diào)用的其他方法拋出的異常。
3.2 代碼實(shí)現(xiàn)
為了讓大家理解throws關(guān)鍵字的用法,接下來繼續(xù)設(shè)計(jì)一個(gè)案例進(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的用法--拋出了兩個(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í)要注意,子類在重寫父類的方法時(shí),如果父類的方法帶有throws聲明,子類方法聲明中的 throws異常,不能出現(xiàn)父類對(duì)應(yīng)方法中throws里沒有的異常類型,即子類方法拋出的異常范圍不能超過父類定義的范圍。也就是說,子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或同類,子類方法聲明拋出的異常不能比父類方法聲明拋出的異常多。
因此利用這一特性,throws也可以用來限制子類的行為。子類方法聲明拋出的異常類型應(yīng)該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多。
3.3 throws執(zhí)行邏輯
根據(jù)上面的案例執(zhí)行結(jié)果,給大家總結(jié)一下throws關(guān)鍵字聲明拋出異常的執(zhí)行邏輯是:
- 如果當(dāng)前方法不知道如何處理某些異常,該異??梢越挥筛弦患?jí)的調(diào)用者來處理,比如main()方法;
- 如果main()方法不知道該如何處理該異常,也可以使用throws關(guān)鍵字繼續(xù)聲明拋出,該異常將交給JVM去處理;
- 最終JVM會(huì)打印出異常的跟蹤棧信息,并中止程序運(yùn)行,這也是程序在遇到異常后自動(dòng)結(jié)束的原因。
3.4 注意事項(xiàng)
我們?cè)谑褂胻hrows時(shí)也要注意如下這些事項(xiàng):
- 只能在方法的定義簽名處聲明可能拋出的異常類型,否則編譯器會(huì)報(bào)錯(cuò);
- 如果一個(gè)方法聲明了拋出異常,但卻沒有在上層的方法體中對(duì)拋出的異常進(jìn)行處理或繼續(xù)拋出該異常,編譯器會(huì)報(bào)錯(cuò);
- throws關(guān)鍵字只是聲明方法可能拋出的異常類型,它并不一定真的會(huì)拋出異常;
- 如果一個(gè)方法中可能會(huì)有多個(gè)異常拋出,可以使用逗號(hào)將它們分隔,如throws Exception1, Exception2, Exception3等;
- 子類方法拋出的異常范圍不能超過父類定義的范圍。
4. throw與throws的區(qū)別
由于throw和throws長(zhǎng)得特別像,功能也有類似之處,為了不讓大家產(chǎn)生迷惑,所以給大家總結(jié)一下throw和throws的區(qū)別:
- throw關(guān)鍵字用來拋出一個(gè)特定的異常對(duì)象,可以使用throw關(guān)鍵字手動(dòng)拋出異常,執(zhí)行throw一定會(huì)拋出某種異常對(duì)象;
- throws關(guān)鍵字用于聲明 一個(gè)方法可能拋出的所有異常信息,表示出現(xiàn)異常的一種可能性,但并不一定會(huì)發(fā)生這些異常 ;
- throw需要用戶自己捕獲相關(guān)的異常,再對(duì)其進(jìn)行相關(guān)包裝,最后將包裝后的異常信息拋出;
- throws通常不必顯示地捕獲異常,可以由系統(tǒng)自動(dòng)將所有捕獲的異常信息拋給上層方法;
- 我們通常在方法或類定義時(shí),通過throws關(guān)鍵字聲明該方法或類可能拋出的異常信息,而在方法或類的內(nèi)部通過throw關(guān)鍵字聲明一個(gè)具體的異常信息。
二. 結(jié)語
至此,就把今天的內(nèi)容講解完畢了,但是異常的學(xué)習(xí)還沒結(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 線程池詳解及創(chuàng)建簡(jiǎn)單實(shí)例
這篇文章主要介紹了Java 線程池詳解及創(chuàng)建簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02SpringBoot整合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ì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Spring boot 路徑映射的實(shí)現(xiàn)
這篇文章主要介紹了spring boot 路徑映射的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11springboot + swagger 實(shí)例代碼
本篇文章主要介紹了springboot + swagger 實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05java調(diào)用WebService服務(wù)的四種方法總結(jié)
WebService是一種跨編程語言、跨操作系統(tǒng)平臺(tái)的遠(yuǎn)程調(diào)用技術(shù),已存在很多年了,很多接口也都是通過WebService方式來發(fā)布的,下面這篇文章主要給大家介紹了關(guān)于java調(diào)用WebService服務(wù)的四種方法,需要的朋友可以參考下2021-11-11Spring boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫(推薦)
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套JPA應(yīng)用框架,可使開發(fā)者用極簡(jiǎn)的代碼即可實(shí)現(xiàn)對(duì)數(shù)據(jù)的訪問和操作。這篇文章主要介紹了Spring-boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫,需要的朋友可以參考下2018-05-05SpringBoot通過參數(shù)注解自動(dòng)獲取當(dāng)前用戶信息的方法
這篇文章主要介紹了SpringBoot通過參數(shù)注解自動(dòng)獲取當(dāng)前用戶信息的方法,文中使用HandlerMethodArgumentResolver 類來實(shí)現(xiàn)這個(gè)功能,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-03-03IDEA創(chuàng)建Maven一直爆紅無法下載的問題解決辦法
這篇文章主要介紹了關(guān)于IDEA創(chuàng)建Maven一直爆紅無法下載的問題的解決辦法,文中圖文結(jié)合的方式給大家講解的非常詳細(xì),對(duì)大家解決辦法非常有用,需要的朋友可以參考下2024-06-06