詳解Java拋出和聲明異常的代碼實現(xiàn)
一. 拋出和聲明異常
1. 概述
我們在編寫代碼時,有時候因為某些原因,并不想在這個方法中立即處理產生的異常,也就是說并不想進行異常的捕獲。那么此時我們可以把這些異常先進行聲明和拋出,即在這個方法中暫時不處理,而是扔給別人去處理。就好比你發(fā)現(xiàn)有個小孩犯了錯,但你不是這個小孩的監(jiān)護人,你來批評處理這個小孩子就不太合適,所以可以把他抓住扔給其父母,讓他的父母來處理這個錯誤。
這就是所謂的異常傳播機制:當某個方法拋出了異常,如果當前方法沒有捕獲該異常,該異常就會被拋到更上層的調用方法,逐層傳遞,直到遇到某個 try ... catch 被捕獲為止。
異常的傳播,在Java中主要是用聲明和拋出異常的關鍵字來實現(xiàn),分別是throws和throw。我們可以使用throws關鍵字在方法上聲明本方法要拋出的異常,使用throw關鍵字拋出某個異常對象。接下來就給大家詳細介紹該如何聲明異常和拋出異常。
2. throw拋出異常
2.1 基本語法
當代碼中發(fā)生了異常,程序會自動拋出一個異常對象,該對象包含了異常的類型和相關信息。但是如果我們想主動拋出異常,則可以使用throw關鍵字來實現(xiàn)。
throw 某個Exception類;
這里的某個Exception類必須是Throwable類或其子類對象。如果是自定義的異常類,也必須是Throwable的直接或間接子類,否則會發(fā)生錯誤。
2.2 代碼實現(xiàn)
接下來給大家設計一個案例,來演示throw的使用:
public class Demo04 { // throw的使用 public static void myMethod(boolean flag) throws Exception { if (flag) { //當flag為true時就拋出一個Exception對象 throw new Exception("主動拋出來的異常對象"); } } public static void caller() { try { //調用myMethod方法 myMethod(true); } catch (Exception e) { e.printStackTrace(); System.out.println("處理主動拋出來的異常:" + e.getMessage()); } } public static void main(String[] args) { caller(); } }
在上面這個案例中,myMethod方法產生了異常,但是自己卻沒有處理,而是進行了拋出。那么會拋給誰呢?我們看到,此時caller方法調用了myMethod方法,即caller方法是myMethod方法的上層調用者,所以myMethod方法中產生的異常就拋給了caller方法。但是如果在caller方法中對這個異常也沒有進行處理,caller方法也會把這個異常繼續(xù)向上層傳遞。這樣逐層向上拋出異常,直到最外層的異常處理程序終止程序并打印出調用棧才會結束。我們來看看異常信息棧的打印結果:
從上面的異常棧信息中可以看出,Exception是在myMethod方法中被拋出的,從下往上看,調用層次依次是:
- main()調用caller();
- caller()調用myMethod();
- myMethod()拋出異常;
而且每層的調用都給出了源代碼的行號,我們可以直接定位到產生異常的代碼行,這樣我們就可以根據(jù)異常信息棧進行異常調試。尤其是要注意,如果異常信息棧中有“Caused by: Xxx”這樣的信息,說明我們捕獲到了造成問題的根源。但是有時候異常信息中可能并沒有“Caused by”這樣的信息,我們可以在代碼中使用Throwable類的getCause()方法來獲取原始異常,此時如果返回了null,說明已經是“根異常”了。這樣有了完整的異常棧的信息,我們才能快速定位并修復代碼的問題。另外我們還要注意,throw關鍵字不會單獨使用,它的使用要符合異常的處理機制。我們一般不會主動拋出一個新的異常對象,而是應該避免異常的產生。
2.3 在try或catch中拋出異常
有些同學很善于思考,于是就提出了問題:如果我們在 try 或者 catch 語句塊中拋出一個異常,那么 finally 語句還能不能執(zhí)行? 為了給大家解答清楚這個問題,再給大家設計一個案例。
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中拋出一個新的運行時異常 throw new RuntimeException(e); } finally { System.out.println("非特殊情況,一定會執(zhí)行finally里的代碼"); } }
我們直接來看上述代碼的執(zhí)行結果:
從上面的結果中可以看出,JVM會先進入到catch代碼塊中進行異常的正常處理,如果發(fā)現(xiàn)在catch中也產生了異常,則會進入到finally中執(zhí)行,finally執(zhí)行完畢后,會再把catch中的異常拋出。所以即使我們在try或catch中拋出了異常,也并不會影響finally的執(zhí)行。
2.4 異常屏蔽
但是這時有的小伙伴又提問了,如果我們在執(zhí)行finally語句中也拋出一個異常,又會怎么樣呢?所以小編繼續(xù)給大家進行論證,我們對上面的案例進行適當改造,在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中拋出一個新的運行時異常 throw new RuntimeException(e); } finally { System.out.println("非特殊情況,一定會執(zhí)行finally里的代碼"); //在finally中也拋出一個異常 throw new IllegalArgumentException("非法參數(shù)異常"); } }
我們還是直接來看看執(zhí)行結果:
從上面的結果中我們可以看出,在finally拋出異常后,原來catch中拋出的異常不見了。這是因為默認情況下只能拋出一個異常,而之前那個沒被拋出的異常稱為“被屏蔽的異常(Suppressed Exception) ”。
2.5 獲取全部異常信息
但是有些較真的小伙伴就說,如果我就想知道所有的異常信息,包括catch中被屏蔽的那些異常信息,這該怎么辦?
我們可以定義一個Exception的origin變量來保存原始的異常信息,然后調用 Throwable對象中的addSuppressed()方法 ,把原始的異常信息添加進來,最后在 finally中繼續(xù) 拋出,并 利用throws關鍵字拋出Exception 。
public class Demo07 { //這里要利用throws關鍵字拋出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關鍵字拋出Exception throw e; } } }
此時的執(zhí)行結果如下所示:
從上面的結果中我們可以看出,即使catch和finally都拋出了異常時,catch異常被屏蔽,但我們通過addSuppressed方法,最終仍然獲取到了完整的異常信息。但是我們也要知道,絕大多數(shù)情況下,都不應該在finally中拋出異常。
3. throws聲明異常
對于方法中不想處理的異常,除了可以利用throw關鍵字進行拋出之外,還有別的辦法嗎?其實我們還可以在該方法的頭部,利用throws關鍵字來聲明這個不想處理的異常,把該異常傳遞到方法的外部進行處理。
3.1 基本語法
throws關鍵字的基本語法如下:
返回值類型 methodName(參數(shù)列表) throws Exception 1,Exception2,…{…}
通過這個語法,我們可以看出,在一個方法中可以利用throws關鍵字同時聲明拋出多個異常:Exception 1,Exception2,… 多個異常之間利用","逗號分隔。如果被調用方法拋出的異常類型在這個異常列表中,則必須在該方法中捕獲或繼續(xù)向上層調用者拋出異常。而這里繼續(xù)聲明拋出的異常,可以是方法本身產生的異常,也可以是調用的其他方法拋出的異常。
3.2 代碼實現(xiàn)
為了讓大家理解throws關鍵字的用法,接下來繼續(xù)設計一個案例進行說明。
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 { //在調用readFile的上層方法中進行異常的捕獲 readFile(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // throws的用法--拋出了兩個異常 public static void readFile() throws FileNotFoundException,IOException { // 定義一個緩沖流對象,以后在IO流階段會細講 BufferedReader reader = null; // 對接一個file.txt文件,該文件可能不存在 reader = new BufferedReader(new FileReader("file.txt")); // 讀取文件中的內容。所有的IO流都可能會產生IO流異常 String line = reader.readLine(); while (line != null) { System.out.println(line); line = reader.readLine(); } } }
在上面的案例中,我們在readFile方法中進行了IO流的操作,此時遇到了兩個異常,但是我們不想在這里進行異常的捕獲,就可以利用throws關鍵字進行異常的聲明拋出。然后main()方法作為調用readFile的上層方法,就需要對異常進行捕獲。當然,如果main方法也不捕獲這兩個異常,該異常就會繼續(xù)向上拋,拋給JVM虛擬機,由虛擬機進行處理。
但是我們在使用throws時要注意,子類在重寫父類的方法時,如果父類的方法帶有throws聲明,子類方法聲明中的 throws異常,不能出現(xiàn)父類對應方法中throws里沒有的異常類型,即子類方法拋出的異常范圍不能超過父類定義的范圍。也就是說,子類方法聲明拋出的異常類型應該是父類方法聲明拋出的異常類型的子類或同類,子類方法聲明拋出的異常不能比父類方法聲明拋出的異常多。
因此利用這一特性,throws也可以用來限制子類的行為。子類方法聲明拋出的異常類型應該是父類方法聲明拋出的異常類型的子類或相同,子類方法聲明拋出的異常不允許比父類方法聲明拋出的異常多。
3.3 throws執(zhí)行邏輯
根據(jù)上面的案例執(zhí)行結果,給大家總結一下throws關鍵字聲明拋出異常的執(zhí)行邏輯是:
- 如果當前方法不知道如何處理某些異常,該異常可以交由更上一級的調用者來處理,比如main()方法;
- 如果main()方法不知道該如何處理該異常,也可以使用throws關鍵字繼續(xù)聲明拋出,該異常將交給JVM去處理;
- 最終JVM會打印出異常的跟蹤棧信息,并中止程序運行,這也是程序在遇到異常后自動結束的原因。
3.4 注意事項
我們在使用throws時也要注意如下這些事項:
- 只能在方法的定義簽名處聲明可能拋出的異常類型,否則編譯器會報錯;
- 如果一個方法聲明了拋出異常,但卻沒有在上層的方法體中對拋出的異常進行處理或繼續(xù)拋出該異常,編譯器會報錯;
- throws關鍵字只是聲明方法可能拋出的異常類型,它并不一定真的會拋出異常;
- 如果一個方法中可能會有多個異常拋出,可以使用逗號將它們分隔,如throws Exception1, Exception2, Exception3等;
- 子類方法拋出的異常范圍不能超過父類定義的范圍。
4. throw與throws的區(qū)別
由于throw和throws長得特別像,功能也有類似之處,為了不讓大家產生迷惑,所以給大家總結一下throw和throws的區(qū)別:
- throw關鍵字用來拋出一個特定的異常對象,可以使用throw關鍵字手動拋出異常,執(zhí)行throw一定會拋出某種異常對象;
- throws關鍵字用于聲明 一個方法可能拋出的所有異常信息,表示出現(xiàn)異常的一種可能性,但并不一定會發(fā)生這些異常 ;
- throw需要用戶自己捕獲相關的異常,再對其進行相關包裝,最后將包裝后的異常信息拋出;
- throws通常不必顯示地捕獲異常,可以由系統(tǒng)自動將所有捕獲的異常信息拋給上層方法;
- 我們通常在方法或類定義時,通過throws關鍵字聲明該方法或類可能拋出的異常信息,而在方法或類的內部通過throw關鍵字聲明一個具體的異常信息。
二. 結語
至此,就把今天的內容講解完畢了,但是異常的學習還沒結束哦。下一篇文章中,會繼續(xù)帶大家學習異常中的新特性,敬請期待哦。
以上就是詳解Java拋出和聲明異常的代碼實現(xiàn)的詳細內容,更多關于Java拋出和聲明異常的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot整合Flyway實現(xiàn)數(shù)據(jù)庫的初始化和版本管理操作
Flyway?是一款開源的數(shù)據(jù)庫版本管理工具,它可以很方便的在命令行中使用,或者在Java應用程序中引入,用于管理我們的數(shù)據(jù)庫版本,這篇文章主要介紹了SpringBoot整合Flyway實現(xiàn)數(shù)據(jù)庫的初始化和版本管理,需要的朋友可以參考下2023-06-06基于java ssm springboot+mybatis酒莊內部管理系統(tǒng)設計和實現(xiàn)
這篇文章主要為大家詳細介紹了java ssm springboot+mybatis實現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08Spring boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫(推薦)
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規(guī)范的基礎上封裝的一套JPA應用框架,可使開發(fā)者用極簡的代碼即可實現(xiàn)對數(shù)據(jù)的訪問和操作。這篇文章主要介紹了Spring-boot中使用Spring-data-jpa方便快捷的訪問數(shù)據(jù)庫,需要的朋友可以參考下2018-05-05SpringBoot通過參數(shù)注解自動獲取當前用戶信息的方法
這篇文章主要介紹了SpringBoot通過參數(shù)注解自動獲取當前用戶信息的方法,文中使用HandlerMethodArgumentResolver 類來實現(xiàn)這個功能,并通過代碼示例講解的非常詳細,需要的朋友可以參考下2024-03-03IDEA創(chuàng)建Maven一直爆紅無法下載的問題解決辦法
這篇文章主要介紹了關于IDEA創(chuàng)建Maven一直爆紅無法下載的問題的解決辦法,文中圖文結合的方式給大家講解的非常詳細,對大家解決辦法非常有用,需要的朋友可以參考下2024-06-06