成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊一聊Java 異常處理基礎篇

開發 后端
計算機程序運行會有個主入口,一般我們稱為 main 方法,main 方法內部也可能調用各種其它方法。當某個方法發生錯誤時,這個方法就會創建一個對象,并把它移交給運行時的系統。這個對象就稱為異常對象,它包含了錯誤相關的信息,包括錯誤類型和程序狀態。

[[410072]]

本文轉載自微信公眾號「蝸牛互聯網」,作者白色蝸牛。轉載本文請聯系蝸牛互聯網公眾號。

閱讀本文你將收獲:

什么是異常

我們日常生活中經常會遇到一些意外的事情,比如坐火車沒帶身份證,那你就無法順利上車。

計算機世界也有類似的情形,術語是異常(Exception),其實是異常事件(Exception Event)的縮寫。

一個異常就是一個事件,它發生在程序執行過程中,會中斷程序的正常運行。好比你上火車沒有身份證,這就是個異常事件,這個事件會阻擋你正常上火車。

計算機程序運行會有個主入口,一般我們稱為 main 方法,main 方法內部也可能調用各種其它方法。當某個方法發生錯誤時,這個方法就會創建一個對象,并把它移交給運行時的系統。這個對象就稱為異常對象,它包含了錯誤相關的信息,包括錯誤類型和程序狀態。

創建異常對象并將其交給運行時系統這個操作就稱為拋出異常。

當方法拋出異常后,運行時系統會嘗試找到處理異常的方法。首先系統會判斷,錯誤發生的方法有沒有處理,如果沒有,會把異常往上層方法拋,直到找到有異常處理的方法。這樣的話,從錯誤發生的方法到異常處理的方法之間,就會形成調用方法的有序列表。

這個方法列表就稱為調用堆棧(call stack)。應用程序的每個方法會按調用順序進棧,棧是先進后出的,比如 main 方法先進棧,開始執行程序,遇到其他方法的調用,其他方法也進棧,其他方法執行完畢,其他方法出棧,繼續執行 main 方法,main 方法執行完畢就出棧,棧空,程序運行結束。

運行時系統會在調用堆棧中尋找包含可以處理異常的代碼塊的方法,這段代碼就稱為異常處理程序。通過調用堆棧,從錯誤發生的方法開始,按照方法調用相反的順序尋找(棧有先進后出的特點)。當找到合適的異常處理程序時,運行時系統就會把異常傳遞給處理程序。如果拋出的異常對象的類型和處理程序可以處理的類型相匹配,就認為異常處理程序是適當的。

選中異常處理程序的過程就稱為捕獲異常。

如果運行時系統找遍了調用堆棧上的所有方法,依然沒有找到適當的異常處理程序,那么運行時系統(以及隨后的程序)將終止。

觀察以下代碼,想想運行情況是怎樣的?

  1. package com.springtest.demo; 
  2.  
  3. public class Test { 
  4.  
  5.     /** 
  6.      * 程序主方法 
  7.      * 
  8.      * @param args 程序入參 
  9.      */ 
  10.     public static void main(String[] args) { 
  11.  
  12.         // 用戶輸入字符串 woniu 
  13.         String woniu = "woniu"
  14.  
  15.         int num = str2number(woniu); 
  16.  
  17.         System.out.println(num); 
  18.     } 
  19.  
  20.     /** 
  21.      * str 轉 整數 
  22.      * 
  23.      * @param str 字符串 
  24.      * @return 整數 
  25.      */ 
  26.     private static int str2number(String str) { 
  27.  
  28.         // 解析成數字,拋 NumberFormatException 
  29.         return Integer.parseInt(str); 
  30.     } 
  31.  

輸出是這樣的:

  1. Exception in thread "main" java.lang.NumberFormatException: For input string: "woniu" 
  2.  at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 
  3.  at java.lang.Integer.parseInt(Integer.java:580) 
  4.  at java.lang.Integer.parseInt(Integer.java:615) 
  5.  at com.springtest.demo.Test.str2number(Test.java:29) 
  6.  at com.springtest.demo.Test.main(Test.java:15) 

觀察運行的結果信息,我們發現應用主程序出現異常了,并且程序終止掉了,因為 num 的值并沒有打印。

結果里也告知我們是出現了 NumberFormatException,也就是數字格式異常,后邊也給到了提示,woniu 這個字符串是轉換不了數字的。這符合我們的預期。

然后就是調用堆棧,調用堆棧里的每一行信息都標明了異常流轉過程中所在的方法路徑,類名以及代碼行數。

其中第一行信息就是異常最先發生的地方,這也可以作為我們異常排查的依據。

很明顯,在 forInputString 拋出異常后,parseInt 和 str2number 都只是轉發異常,并沒有捕獲異常,甚至在 main 方法中,也沒捕獲異常。最后因為沒有異常處理程序,而導致程序運行終止。

如何捕獲和處理異常

為了程序能夠正常運行不被意外終止,Java 編程規范就有要求:異常必須要捕獲或者指定。

使用 try

捕獲異常的第一步是用 try 把可能引發異常的代碼括起來。

語法如下:

  1. try { 
  2.     // 可能引發異常的代碼 

try 包括了一個代碼塊,你可以把可能引發異常的代碼放里邊。代碼可以是一行,也可以是多行。這也意味著這個代碼塊可能引發多種不同的異常。

異常處理程序只有 try 是無法通過編譯的。你用 javac 命令編譯只有 try 的 java 文件,會報以下錯誤:

錯誤: 'try' 不帶有 'catch', 'finally' 或資源聲明

  1. 錯誤: 'try' 不帶有 'catch''finally' 或資源聲明 
  2.         try { 
  3.         ^ 
  4. 1 個錯誤 

所以 try 代碼塊只是圈定了捕獲異常的范圍,只靠 try 做異常管理顯然不夠。

使用 catch

catch 語法

因此捕獲異常就需要第二步:用 catch 捕獲異常和異常處理。

語法如下:

  1. try { 
  2.     // 可能引發異常的代碼 
  3. } catch (ExceptionType1 name1) { 
  4.     // 命中異常類型1 ExceptionType1 時的異常處理代碼 
  5. } catch (ExceptionType2 name2) { 
  6.     // 命中異常類型2 ExceptionType2 時的異常處理代碼 

catch 是搭配 try 使用的,不單獨出現。try 后邊可以跟多個 catch 代碼塊,以處理 try 中出現的多種類型的異常。

每個 catch 代碼塊都是一個異常處理程序,處理的時候由 catch 的參數指定異常類型。

catch 的圓括號里,參數 ExceptionType 聲明了這個處理程序可以處理的異常類型,這個異常類型必須是從 Throwable 類繼承的類。

Java 異常的繼承體系

提到 Throwable 就不得不說 Java 的異常體系。以下是 Java 異常的繼承體系圖。

Throwable 是異常體系的根,它繼承自 Object。Throwable 又拆分成兩個體系:Error 和 Exception。

Error 表示嚴重的錯誤,程序一般無法處理,比如表示棧溢出的 StackOverflowError。

Exception 表示運行時的錯誤,它是可以被捕獲并處理的。Exception 又可以拆分為兩類:RuntimeException 和 Checked Exception。

RuntimeException 指運行時異常,它是程序邏輯編寫不對造成的,比如表示空指針異常的 NullPointerException 以及表示數組索引越界的 IndexOutOfBoundsException。出現這種異常就是代碼 Bug,應該修復程序代碼。

  1. int[] arrry = {0,1,2}; 
  2.  
  3. // 此處會拋 java.lang.ArrayIndexOutOfBoundsException,不應該出現 arrry[3] 這樣的代碼 
  4. System.out.println(arrry[3]); 

Checked Exception 指檢測型異常,它是程序邏輯的一部分。比如表示 IO 異常的 IOException 以及表示文件找不到的 FileNotFoundException。這種異常必須捕獲并處理,否則編譯會失敗。

以下代碼是不能編譯通過的:

  1. public class A { 
  2.     public static void main(String[] args) { 
  3.  
  4.         FileInputStream inputStream = new FileInputStream("/"); 
  5.  
  6.     } 

javac 編譯會報以下錯誤,也會提示你必須用 try/catch 捕獲或者把異常添加到聲明里方便拋出。

  1. 錯誤: 未報告的異常錯誤FileNotFoundException; 必須對其進行捕獲或聲明以便拋出 
  2.         FileInputStream inputStream = new FileInputStream("/"); 
  3.                                       ^ 
  4. 1 個錯誤 

catch 使用

回到 catch 語法中,ExceptionType 對應的就是 Java 異常體系中的 Exception 類或者它的子類。

name 是為異常類型起的名稱,花括號里的內容就是調用異常處理程序時執行的代碼,這里的代碼可以通過 name 這個名稱引用異常。

當調用堆棧出現異常時,運行時系統會調用異常處理程序,當異常處理程序的 ExceptionType 和引發異常的類型匹配時,即命中某個 catch 塊,就會把異常對象分配給異常處理程序的參數,進而執行 catch 塊的異常處理代碼。

異常處理程序我們可以做很多事情,比如打印錯誤日志,暫停程序,執行錯誤恢復,也可以提示給用戶,或者把異常往上層傳遞。以下是打印錯誤信息的示例代碼:

  1. public static void main(String[] args) { 
  2.  
  3.     try { 
  4.  
  5.         int[] arrry = {0, 1, 2}; 
  6.  
  7.         // 此處會拋 java.lang.ArrayIndexOutOfBoundsException,不應該出現 arrry[3] 這樣的代碼 
  8.         System.out.println(arrry[3]); 
  9.  
  10.     } catch (IndexOutOfBoundsException e) { 
  11.  
  12.         System.out.println("捕獲到數組越界異常:"); 
  13.         System.out.println(e); 
  14.     } 

輸出結果為:

  1. 捕獲到數組越界異常: 
  2. java.lang.ArrayIndexOutOfBoundsException: 3 

有些場景,我們的一段代碼可能引發多種異常,而異常的處理會比較一致,比如都是打印日志,這種情況下,如果都單獨設置一個 catch 塊,寫相同的代碼,重復度就很高。

因此在 Java 7 之后,一個 catch 塊就支持處理多種類型的異常。語法如下:

  1. try { 
  2.     // 可能引發異常的代碼 
  3. } catch (ExceptionType1 name1 | ExceptionType2 name2) { 
  4.     // 命中異常類型1 ExceptionType1 或異常類型2 ExceptionType2 時的異常處理代碼 
  5. }  

使用 finally

程序在運行的時候有時候會打開一些資源,比如文件,連接,線程等等。如果程序運行中途拋異常,程序終止,打開的資源就永遠得不到釋放了,這會導致資源泄漏,甚至系統崩潰。

再比如,程序運行結束前,我要輸出一個摘要日志做監控,但如果運行中途拋異常,程序終止,日志就不會打印,我也看不到我想要的信息。

因此需要有種機制,能夠支持在異常發生,阻斷流程的時候,也能把打開的資源釋放掉或者執行指定的邏輯。

Java 用 finally 來達成這種目的,finally 可以形成try-finally 結構,也可以形成 try-catch-finally 結構。但是 finally 代碼塊總是在 try 退出時執行。

這個「總是」可以分為以下幾種情況:

無異常

try 執行完畢,未發生異常,然后執行 finally 代碼塊,像普通程序一樣順序執行。

  1. public static void main(String[] args) { 
  2.  
  3.     System.out.println("main:" + fetchMyName()); 
  4.  
  5.  
  6. public static String fetchMyName() { 
  7.  
  8.     String me = "woniu"
  9.  
  10.     try { 
  11.         me = "woniu666"
  12.  
  13.     } finally { 
  14.         System.out.println("finally: " + me); 
  15.     } 
  16.  
  17.     return me; 

輸出:

  1. finally: woniu666 
  2. main:woniu666 

有異常未捕獲

try 執行過程中出現異常,會把異常對象拋出,但 finally 代碼塊依然會執行。

  1. public static void main(String[] args) { 
  2.  
  3.     System.out.println("main:" + fetchMyName()); 
  4.  
  5.  
  6. public static String fetchMyName() { 
  7.  
  8.     String me = "woniu"
  9.     int[] arrry = {0, 1, 2}; 
  10.  
  11.     try { 
  12.         me = "woniu666"
  13.  
  14.         // 此處會拋 java.lang.ArrayIndexOutOfBoundsException,不應該出現 arrry[3] 這樣的代碼 
  15.         System.out.println(arrry[3]); 
  16.  
  17.     } finally { 
  18.         System.out.println("finally: " + me); 
  19.     } 
  20.  
  21.     return me; 

fetchMyName() 未捕獲到異常,就往上拋,但會把 finally 里的邏輯先執行掉,在 main 方法中同樣沒有捕獲異常,于是就阻斷了程序,打印出了調用堆棧。

  1. finally: woniu666 
  2. Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3 
  3.  at com.springtest.demo.TryFinally.fetchMyName(TryFinally.java:28) 
  4.  at com.springtest.demo.TryFinally.main(TryFinally.java:15) 

有異常有捕獲

try 執行過程中出現異常,會把異常對象拋出,catch 捕獲異常并正常處理,此時 finally 代碼塊依然會執行。

  1. public static void main(String[] args) { 
  2.  
  3.     System.out.println("main:" + fetchMyName()); 
  4.  
  5.  
  6. public static String fetchMyName() { 
  7.  
  8.     String me = "woniu"
  9.     int[] arrry = {0, 1, 2}; 
  10.  
  11.     try { 
  12.         me = "woniu666"
  13.  
  14.         // 此處會拋 java.lang.ArrayIndexOutOfBoundsException,不應該出現 arrry[3] 這樣的代碼 
  15.         System.out.println(arrry[3]); 
  16.  
  17.     } catch (ArrayIndexOutOfBoundsException e) { 
  18.         System.out.println("命中數組索引越界異常的處理器,越界索引為:" + e.getMessage()); 
  19.  
  20.     } finally { 
  21.         System.out.println("finally: " + me); 
  22.     } 
  23.  
  24.     return me; 

代碼正常運行,先執行了 catch 代碼塊中的邏輯,然后執行了 finally 代碼塊,最后執行 main 方法。

  1. 命中數組索引越界異常的處理器,越界索引為:3 
  2. finally: woniu666 
  3. main:woniu666 

try 中 return

return 意味著方法執行結束,而 finally 是在 try 退出時執行,那如果 try 代碼塊中帶 return,finally 代碼塊還會執行到么?

try 代碼塊加個 return 試試!

  1. public static void main(String[] args) { 
  2.  
  3.     System.out.println("main:" + fetchMyName()); 
  4.  
  5.  
  6. public static String fetchMyName() { 
  7.  
  8.     String me = "woniu"
  9.     int[] arrry = {0, 1, 2}; 
  10.  
  11.     try { 
  12.         me = "woniu666"
  13.  
  14.         // 此處不拋異常 
  15.         System.out.println(arrry[0]); 
  16.  
  17.         return "try"
  18.  
  19.     } catch (ArrayIndexOutOfBoundsException e) { 
  20.         System.out.println("命中數組索引越界異常的處理器,越界索引為:" + e.getMessage()); 
  21.  
  22.     } finally { 
  23.         System.out.println("finally: " + me); 
  24.     } 
  25.  
  26.     return me; 

看結果依然會走到 finally 代碼塊的執行!

  1. finally: woniu666 
  2. main:try 

catch 中 return

try 中 return 我們試了,那 catch 中 return,finally 的執行是啥樣的呢?

  1. public static void main(String[] args) { 
  2.  
  3.     System.out.println("main:" + fetchMyName()); 
  4.  
  5.  
  6. public static String fetchMyName() { 
  7.  
  8.     String me = "woniu"
  9.     int[] arrry = {0, 1, 2}; 
  10.  
  11.     try { 
  12.         me = "woniu666"
  13.  
  14.         // 此處會拋 java.lang.ArrayIndexOutOfBoundsException,不應該出現 arrry[3] 這樣的代碼 
  15.         System.out.println(arrry[3]); 
  16.  
  17.     } catch (ArrayIndexOutOfBoundsException e) { 
  18.         System.out.println("命中數組索引越界異常的處理器,越界索引為:" + e.getMessage()); 
  19.  
  20.         return "catch"
  21.  
  22.     } finally { 
  23.         System.out.println("finally: " + me); 
  24.     } 
  25.  
  26.     return me; 

看結果依然會走到 finally 代碼塊的執行!

  1. 命中數組索引越界異常的處理器,越界索引為:3 
  2. finally: woniu666 
  3. main:catch 

如何指定方法拋出的異常

異常捕獲的知識介紹完之后,你想象另外一種情況,就是當前方法拋出異常后,但是呢,當前方法不適合處理這個異常,而調用堆棧上層的方法更適合處理。那其實當前方法最好就不要捕獲異常,并能夠允許調用堆棧上層的方法處理它。

此時,如果拋出的異常是 檢查型異常,那你就必須在方法上指定它可以拋出這些異常。你需要在方法聲明中添加一個 throws 語句。throws 語句包含 throws 關鍵字,后面跟著由該方法一引發的所有異常,多個異常用逗號分隔。throws 語句放在方法名和參數列表之后,放在定義方法范圍的圓括號之前。

代碼示例如下:

  1. public static void test() throws FileNotFoundException { 
  2.  
  3.     FileInputStream inputStream = new FileInputStream("/"); 
  4.  

由上層 main 方法捕獲處理:

  1. public static void main(String[] args) { 
  2.  
  3.     try { 
  4.         test(); 
  5.  
  6.     } catch (FileNotFoundException e) { 
  7.  
  8.         System.out.println("文件找不到異常:" + e.getMessage()); 
  9.     } 
  10.  

可以正常輸出:

  1. 文件找不到異常:/ (Is a directory) 

前邊說檢查型異常必須要處理,是因為不處理會編譯不通過,要么捕獲和處理異常,要么指定方法拋出的異常,

那非檢查型異常,也就是運行時異常也有這種要求么?

非檢查型異常并不強制,你可以指定方法拋出的異常,也可以不指定,不指定的時候,異常對象會不停的沿著調用堆棧向上層拋,直到被捕獲處理或者程序終止。

小結

本文介紹了異常的概念,我們了解到了異常相關的術語,異常出現的背景以及異常的運行機制,接著我們按照 Java 編程規范分別介紹了異常如何捕獲以及異常如何指定,同時也介紹了 Java 異常的繼承體系。這些都是非常基礎的內容,卻也非常重要,寫代碼的時候必須要考慮這方面,甚至你可以認為,面向異常編程非常考驗你的編碼功底。

 

責任編輯:武曉燕 來源: 蝸牛互聯網
相關推薦

2022-05-12 23:19:15

Redis內存碎片處理

2022-07-19 08:01:08

Azure云環境安全

2017-12-26 14:56:44

虛擬化基礎知識

2022-08-08 08:25:21

Javajar 文件

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2018-06-07 13:17:12

契約測試單元測試API測試

2023-09-29 08:58:38

2020-12-11 11:11:44

原子類JavaCAS

2019-08-29 09:30:20

Java泛型構造器

2019-02-13 14:15:59

Linux版本Fedora

2021-08-04 09:32:05

Typescript 技巧Partial

2021-01-29 08:32:21

數據結構數組

2021-02-06 08:34:49

函數memoize文檔

2023-07-06 13:56:14

微軟Skype

2023-05-15 08:38:58

模板方法模式

2022-11-01 08:46:20

責任鏈模式對象

2018-11-29 09:13:47

CPU中斷控制器

2020-10-15 06:56:51

MySQL排序
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产在线拍偷自揄拍视频 | 99久久精品免费视频 | 91正在播放 | 一区二视频 | 日韩欧美高清 | 亚洲交性 | 国产成人在线视频 | 国产成人精品免费视频 | 欧产日产国产精品国产 | 欧美成人精品二区三区99精品 | 国产精品免费观看 | 亚洲成人免费在线 | 另类专区亚洲 | 亚洲高清在线 | 中文字幕不卡视频在线观看 | 精品国产99 | www.av7788.com| 欧美日韩第一页 | 国产目拍亚洲精品99久久精品 | 久草网址| 日韩aⅴ在线观看 | 国产精品精品久久久久久 | 免费网站在线 | 黄色片免费看视频 | 成人久久久久久久久 | 一区二区三区欧美在线 | 一区在线视频 | 欧州一区二区三区 | 五月综合色啪 | 蜜桃日韩 | 亚洲小说图片 | 精品视频久久久久久 | 亚洲a一区二区 | 国产精品久久久久久久久久久久久 | 日韩中文在线观看 | 男人的天堂在线视频 | 日韩国产专区 | 精品欧美一区二区三区久久久 | 亚洲国产精品一区二区三区 | 欧美国产一区二区 | 久久国产精品无码网站 |