Java 中的异常和处理详解
2019獨角獸企業重金招聘Python工程師標準>>>
簡介
程序運行時,發生的不被期望的事件,它阻止了程序按照程序員的預期正常執行,這就是異常。異常發生時,是任程序自生自滅,立刻退出終止,還是輸出錯誤給給用戶?或者用 C 語言風格:用函數返回值作為執行狀態?
Java 提供了更加優秀解決辦法:異常處理機制。
異常處理機制能讓程序在異常發生時,按照代碼預先設定的異常處理邏輯,針對性地處理異常,讓程序盡最大可能恢復正常并繼續執行,且保持代碼的清晰。
Java 中的異常可以是函數中語句執行時引發的,也可以是程序員通過 throw 語句手動拋出的,只要在 Java 程序中產生了異常,就會用一個對應類型的異常對象來封裝異常,JRE 就會試圖尋找異常處理程序來處理異常。
Throwable 類是 Java 異常類型的頂層父類,一個對象只有是 Throwable 類的(直接或間接)實例,它才是一個異常對象,才能被異常處理機制識別。JDK 中內建了一些常用的異常類,我們也可以自定義異常。
Java 異常的分類和類結構圖
Java 標準庫內建了一些通用的異常,這些類以 Throwable 為頂層父類。
Throwable 又派生出 Error 類和 Exception 類。
錯誤:Error 類以及它的子類的實例,代表 JVM 本身的錯誤。錯誤不能被程序員通過代碼處理,Error 很少出現。因此,程序員應該關注 Exception 為父類的分支下的各種異常類。
異常:Exception 以及它的子類,代表程序運行時發送的各種不被期望發生的事件。可以被 Java 異常處理機制使用,是異常處理的核心。
總體上我們根據 Java 對異常的處理要求,將異常分為兩類。
非檢查異常(unchecked exception)
Error 和 RuntimeException 以及它們的子類。Java 在編譯時,不會提示和發現這樣的異常,不要求在程序中處理這些異常。所以如果愿意,我們可以編寫代碼處理(使用 try...catch...finally)這樣的異常,也可以不處理。對于這些異常,我們更應該的不是去處理這些異常,而是應該修正代碼。這樣的異常發生的原因多半是代碼邏輯寫的有問題。如除 0 錯誤 ArithmeticException,錯誤的強制類型轉換錯誤 ClassCastException,數組索引越界錯誤 ArrayIndexOutOfBoundsException,操作了空對象錯誤 NullPointerException 等等。
檢查異常(checked exception)
除了 Error 和 RuntimeException 的其它異常。Java 強制要求程序員為這樣的異常做預備處理工作(使用 try...catch...finally 或者 throws)。在方法中要么用 try...catch 語句捕獲它并處理,要么用 throws 子句聲明拋出它,否則編譯不會通過。這樣的異常一般是由程序的運行環境導致的。因為程序可能運行被運行在各種未知的環境下,而程序員無法干預用戶如何使用他編寫的程序,于是程序員就應該為這樣的異常時刻準備著。如 SQLException,IOException,ClassNotFoundException 等。
初識異常
下面的代碼會演示2個異常類型:ArithmeticException 和 InputMimatchException。前者由于整數除 0 引發,后者是輸入的數據不能被轉化為 int 類型引發。
package com.example; import java. util .Scanner ; public class AllDemo {public static void main (String [] args ){System . out. println( "----歡迎使用命令行除法計算器----" ) ;CMDCalculate ();}public static void CMDCalculate (){Scanner scan = new Scanner ( System. in );int num1 = scan .nextInt () ;int num2 = scan .nextInt () ;int result = devide (num1 , num2 ) ;System . out. println( "result:" + result) ;scan .close () ;}public static int devide (int num1, int num2 ){return num1 / num2 ;} } /*****************************************----歡迎使用命令行除法計算器---- 0 Exception in thread "main" java.lang.ArithmeticException : / by zeroat com.example.AllDemo.devide( AllDemo.java:30 )at com.example.AllDemo.CMDCalculate( AllDemo.java:22 )at com.example.AllDemo.main( AllDemo.java:12 )----歡迎使用命令行除法計算器---- r Exception in thread "main" java.util.InputMismatchExceptionat java.util.Scanner.throwFor( Scanner.java:864 )at java.util.Scanner.next( Scanner.java:1485 )at java.util.Scanner.nextInt( Scanner.java:2117 )at java.util.Scanner.nextInt( Scanner.java:2076 )at com.example.AllDemo.CMDCalculate( AllDemo.java:20 )at com.example.AllDemo.main( AllDemo.java:12 ) *****************************************/異常是在執行某個函數時引發的,而函數又是層級調用,形成調用棧的,因此,只要一個函數發生了異常,那么它的所有 caller 都會被異常影響。當這些被影響的函數以異常信息輸出時,就形成了異常追蹤棧。
異常最先發生的地方,叫做異常拋出點。
從上面的例子可以看出,當 devide 函數發生除 0 異常時,devide 函數拋出 ArithmeticExcepton 異常,因此調用它的 CMDCalculate 函數也無法正常完成,因此也發送異常,而 CMDCalculate 的 caller --main 因為 CMDCalculate 拋出異常,也發生了異常,這樣一直向調用棧的棧底回溯。這種行為叫做異常的冒泡,異常的冒泡是為了在當前發生異常的函數或者這個函數的 caller 中找到最近的異常處理程序。由于這個例子沒有使用任何異常處理機制,因此異常最終由 main 函數拋給 JRE,導致程序終止。
上面的代碼不使用異常處理機制,也可以順利編譯,因為2個異常都是非檢查異常。但是下面的例子就必須使用異常處理機制,因為異常是檢查異常。
代碼中選擇使用 throws 聲明異常,讓函數的調用者去處理可能發生的異常。但是為什么只 throws 了 IOException呢?因為 FileNotFoundException 是 IOException 的子類,在處理范圍內。
@Test public void testException() throws IOException {//FileInputStream的構造函數會拋出FileNotFoundExceptionFileInputStream fileIn = new FileInputStream("E:\\a.txt");int word;//read方法會拋出IOExceptionwhile((word = fileIn.read())!=-1) {System.out.print((char)word);}//close方法會拋出IOExceptionfileIn.clos }異常處理的基本語法
在編寫代碼處理異常時,對于檢查異常,有2中不同的處理方式:使用 try...catch...finally 語句塊處理它。或者,在函數簽名中使用 throws 聲明交給函數調用者 caller 去處理。
try...catch...finally 語句塊
注:
有的編程語言當異常被處理后,控制流會恢復到異常拋出點接著執行,這種策略叫做:resumption model of exception handing(恢復式異常處理模式)
而 Java 則是讓執行流恢復到處理了異常的 catch 塊后接著執行,這種策略叫做:termination model of exception handing(終結式異常處理模式)
throws 函數聲明
throws 聲明:如果一個方法內部的代碼會拋出檢查異常(checked exception),而方法自己又沒有完全處理掉,則 Java 保證你必須在方法的簽名上使用 throws 關鍵字聲明這些可能拋出的異常,否則編譯不通過。
throws 時另一種處理異常的方式,它不同于 try...catch...finally,throws 僅僅時將函數中可能出現的異常向調用者聲明,而自己則不具體處理。
采取這種異常處理的原因可能是:方法本身不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者需要為可能發生的異常負責。
finally 塊
finally 塊不管異常是否發生,只要對應的 try 執行了,則它一定也執行。只有一種方法可以讓 finally 塊不執行:System.exit(0)。因此 finally 塊通常用來做資源釋放操作:關閉文件,關閉數據庫連接等等。
注:
throw 異常拋出語句
程序員也可以通過 throw 語句手動顯示的拋出一個異常。throw 語句的后面必須是一個異常對象。
throw 語句必須寫在函數中,執行 throw 語句的地方就是一個異常拋出點,它和有 JRE 自動形成的異常拋出點沒有任何差別。
異常的鏈化
在一些大型的,模塊化的軟甲開發中,一旦一個地方發生異常,則如骨牌效應一般,將導致一連串的異常。假設 B 模塊完成自己的邏輯需要調用 A 模塊中的方法,如果 A 模塊發生異常,則 B 也將不能完成而發生異常,但是 B 在拋出異常時,會將 A 的異常信息掩蓋掉,這將使得異常的根源信息丟失。異常的鏈化可以將多個模塊的異常串聯起來,使得異常信息不會丟失。
異常鏈化:以一個異常對象為參數構造新的異常對象。新的異常對象將包含先前異常的信息。這項技術主要是異常類的一個帶 Throwable 參數的函數來實現的。這個當作參數的異常,我們叫它根源異常(cause)。
查看 Throwable 類源碼,可以發現里面有一個 Throwable 字段 cause,就是它保存了構造時傳遞的根源異常參數。這種設計和鏈表的節點類設計如出一轍,因此形成鏈也是自然的了。
自定義異常
如果要自定義異常,則擴展 Exception 類即可,因此這樣的自定義異常都屬于檢查異常。如果要自定義非檢查異常,則擴展自 RuntimeException。
自定義的異常應該總是包含如下的構造函數:
- 一個無參構造函數
- 一個帶有 String 參數的構造函數,并傳遞給父類的構造函數
- 一個帶有 String 參數和 Throwable 參數,并都傳遞給父類構造函數
- 一個帶有 Throwable 參數的構造函數,并傳遞給父類的構造函數
下面是 IOException 類的完成源代碼,可以借鑒。
public class IOException extends Exception {static final long serialVersionUID = 7818375828146090155L;public IOException(){super();}public IOException(String message){super(message);}public IOException(String message, Throwable cause){super(message, cause);}public IOException(Throwable cause){super(cause);} }異常的注意事項
例如,父類方法 throws 的是2個異常,子類就不能 throws 3個及以上的異常。父類 throws IOException,子類就必須 throws IOException 或者 IOException 的子類。
也就是說,Java 中的異常是線程獨立的,線程的問題應該由線程自己來解決,而不要委托到外部,也不會直接影響到其它線程的執行。
finally 塊和 return
首先一個不容易理解的事實:在 try 塊中即便有 return,break,continue 等改變執行流的語句,finally 也會執行。
public static void main(String[] args) {int re = bar();System.out.println(re); } private static int bar() {try{return 5;} finally{System.out.println("finally");} } /*輸出: finally */也就是說:try...catch.finally 中的 return 只要能執行,就都執行了,它們共同向同一個內存地址(假設地址是 0x80)寫入返回值,后執行的將覆蓋先執行的數據,而真正被調用者取的返回值就是最后一次寫入的。那么,按照這個思想,下面的這個例子也就不難理解了。
finally 中的 return 會覆蓋 try 或者 catch 中的返回值。
finally 中的 return 會抑制(消滅)前面 try 或者 catch 塊中的異常
class TestException {public static void main(String[] args){int result;try{result = foo();System.out.println(result); //輸出100} catch (Exception e){System.out.println(e.getMessage()); //沒有捕獲到異常}try{result = bar();System.out.println(result); //輸出100} catch (Exception e){System.out.println(e.getMessage()); //沒有捕獲到異常}}//catch中的異常被抑制@SuppressWarnings("finally")public static int foo() throws Exception{try {int a = 5/0;return 1;}catch(ArithmeticException amExp) {throw new Exception("我將被忽略,因為下面的finally中使用了return");}finally {return 100;}}//try中的異常被抑制@SuppressWarnings("finally")public static int bar() throws Exception{try {int a = 5/0;return 1;}finally {return 100;}} }finally 中的異常會覆蓋(消滅)前面 try 或者 catch 中的異常
class TestException {public static void main(String[] args){int result;try{result = foo();} catch (Exception e){System.out.println(e.getMessage()); //輸出:我是finaly中的Exception}try{result = bar();} catch (Exception e){System.out.println(e.getMessage()); //輸出:我是finaly中的Exception}}//catch中的異常被抑制@SuppressWarnings("finally")public static int foo() throws Exception{try {int a = 5/0;return 1;}catch(ArithmeticException amExp) {throw new Exception("我將被忽略,因為下面的finally中拋出了新的異常");}finally {throw new Exception("我是finaly中的Exception");}}//try中的異常被抑制@SuppressWarnings("finally")public static int bar() throws Exception{try {int a = 5/0;return 1;}finally {throw new Exception("我是finaly中的Exception");}} }上面的3個例子都異于常人的編碼思維,因此建議:
- 不要在 finally 中使用 return
- 不要在 finally 中拋出異常
- 減輕 finally 的任務,不要在 finally 中做一些其它的事情,finally 塊僅僅用來釋放資源是最合適的
- 盡量將所有的 return 寫在函數的最后面,而不是 try...catch...finally 中
轉載于:https://my.oschina.net/u/4132929/blog/3050585
總結
以上是生活随笔為你收集整理的Java 中的异常和处理详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王者荣耀改性别要多久
- 下一篇: Java的新项目学成在线笔记-day14