《Java從小白到大牛》紙質版已經上架了!!!html
不少事件並不是老是按照人們本身設計意願順利發展的,而是有可以出現這樣那樣的異常狀況。例如:你計劃週末郊遊,你的計劃會安排滿滿的,你計劃多是這樣的:從家裏出發→到達目的→游泳→燒烤→回家。但天有不測風雲,當前你準備燒烤時候天降大雨,你只能終止郊遊提早回家。「天降大雨」是一種異常狀況,你的計劃應該考慮到這樣狀況,而且應該有處理這種異常的預案。java
爲加強程序的健壯性,計算機程序的編寫也須要考慮處理這些異常狀況,Java語言提供了異常處理功能,本章介紹Java異常處理機制。程序員
爲了學習Java異常處理機制,首先看看下面程序。編程
//HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { int a = 0; System.out.println(5 / a); } }
這個程序沒有編譯錯誤,但會發生以下的運行時錯誤:網絡
Exception in thread "main" java.lang.ArithmeticException: / by zero at com.a51work6.HelloWorld.main(HelloWorld.java:9)
在數學上除數不能爲0,因此程序運行時表達式(5 / a)會拋出ArithmeticException異常,ArithmeticException是數學計算異常,凡是發生數學計算錯誤都會拋出該異常。ide
程序運行過程當中不免會發生異常,發生異常並不可怕,程序員應該考慮到有可能發生這些異常,編程時應該捕獲並進行處理異常,不能讓程序發生終止,這就是健壯的程序。學習
異常封裝成爲類Exception,此外,還有Throwable和Error類,異常類繼承層次如圖14-1所示。設計
從圖14-1可見,全部的異常類都直接或間接地繼承於java.lang.Throwable類,在Throwable類有幾個很是重要的方法:3d
提示 堆棧跟蹤是方法調用過程的軌跡,它包含了程序執行過程當中方法調用的順序和所在源代碼行號。指針
爲了介紹Throwable類的使用,下面修改14.1節的示例代碼以下:
//HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { int a = 0; int result = divide(5, a); System.out.printf("divide(%d, %d) = %d", 5, a, result); } public static int divide(int number, int divisor) { try { return number / divisor; } catch (Throwable throwable) { ① System.out.println("getMessage() : " + throwable.getMessage()); ② System.out.println("toString() : " + throwable.toString()); ③ System.out.println("printStackTrace()輸出信息以下:"); throwable.printStackTrace(); ④ } return 0; } }
運行結果以下:
getMessage() : / by zero toString() : java.lang.ArithmeticException: / by zero printStackTrace()輸出信息以下: java.lang.ArithmeticException: / by zero at com.a51work6.HelloWorld.divide(HelloWorld.java:17) at com.a51work6.HelloWorld.main(HelloWorld.java:10) divide(5, 0) = 0
將能夠發生異常的語句System.out.println(5 / a)放到try-catch代碼塊中,稱爲捕獲異常,有關捕獲異常的相關知識會在下一節詳細介紹。在catch中有一個Throwable對象throwable,throwable對象是系統在程序發生異常時建立,經過throwable對象能夠調用Throwable中定義的方法。
代碼第②行是調用getMessage()方法得到異常消息,輸出結果是「/ by zero」。代碼第③行是調用toString()方法得到異常對象的描述,輸出結果是java.lang.ArithmeticException: / by zero。代碼第④行是調用printStackTrace()方法打印異常堆棧跟蹤信息。
提示 堆棧跟蹤信息從下往上,是方法調用的順序。首先JVM調用是com.a51work6.HelloWorld類的main方法,接着在HelloWorld.java源代碼第10行調用com.a51work6.HelloWorld類的divide方法,在HelloWorld.java源代碼第17行發生了異常,最後輸出的是異常信息。
從圖14-1可見,Throwable有兩個直接子類:Error和Exception。
Error是程序沒法恢復的嚴重錯誤,程序員根本無能爲力,只能讓程序終止。例如:JVM內部錯誤、內存溢出和資源耗盡等嚴重狀況。
Exception是程序能夠恢復的異常,它是程序員所能掌控的。例如:除零異常、空指針訪問、網絡鏈接中斷和讀取不存在的文件等。本章所討論的異常處理就是對Exception及其子類的異常處理。
從圖14-1可見,Exception類能夠分爲:受檢查異常和運行時異常。
如圖14-1所示,受檢查異常是除RuntimeException之外的異常類。它們的共同特色是:編譯器會檢查這類異常是否進行了處理,即要麼捕獲(try-catch語句),要麼不拋出(經過在方法後聲明throws),不然會發生編譯錯誤。它們種類不少,前面遇到過的日期解析異常ParseException。
運行時異常是繼承RuntimeException類的直接或間接子類。運行時異常每每是程序員所犯錯誤致使的,健壯的程序不該該發生運行時異常。它們的共同特色是:編譯器不檢查這類異常是否進行了處理,也就是對於這類異常不捕獲也不拋出,程序也能夠編譯經過。因爲沒有進行異常處理,一旦運行時異常發生就會致使程序的終止,這是用戶不但願看到的。因爲14.2.1節除零示例的ArithmeticException異常屬於RuntimeException異常,見圖14-1所示,能夠不用加try-catch語句捕獲異常。
提示 對於運行時異常一般不採用拋出或捕獲處理方式,而是應該提早預判,防止這種發生異常,作到未雨綢繆。例如14.2.1節除零示例,在進行除法運算以前應該判斷除數是非零的,修改示例代碼以下,從代碼可見提早預判這樣處理要比經過try-catch捕獲異常要友好的多。
//HelloWorld.java文件
package com.a51work6;
public class HelloWorld {
public static void main(String[] args) {
int a = 0;
int result = divide(5, a);
System.out.printf("divide(%d, %d) = %d", 5, a, result);
}
public static int divide(int number, int divisor) {
//判斷除數divisor非零,防止運行時異常
if (divisor != 0) {
return number / divisor;
}
return 0;
}
}
除了圖14-1所示異常,還有不少異常,本書不能一一窮盡,隨着學習的深刻會介紹一些經常使用的異常,其餘異常讀者能夠本身查詢API文檔。 ## 捕獲異常 在學習本內容以前,你先考慮一下,在現實生活中是如何對待領導交給你的任務呢?固然無非是兩種:本身有能解決的本身處理;本身無力解決的反饋給領導,讓領導本身處理。 那麼對待受檢查異常亦是如此。當前方法有能力解決,則捕獲異常進行處理;沒有能力解決,則拋出給上層調用方法處理。若是上層調用方法還無力解決,則繼續拋給它的上層調用方法,異常就是這樣向上傳遞直到有方法處理它,若是全部的方法都沒有處理該異常,那麼JVM會終止程序運行。 這一節先介紹一下捕獲異常。 ### try-catch語句 {#try-catch} 捕獲異常是經過try-catch語句實現的,最基本try-catch語句語法以下: ```java try{ //可能會發生異常的語句 } catch(Throwable e){ //處理異常e }
try代碼塊中應該包含執行過程當中可能會發生異常的語句。一條語句是否有可能發生異常,這要看語句中調用的方法。例如日期格式化類DateFormat的日期解析方法parse(),該方法的完整定義以下:
public Date parse(String source) throws ParseException
方法後面的throws ParseException說明:當調用parse()方法時有能夠能產生ParseException異常。
提示 靜態方法、實例方法和構造方法均可以聲明拋出異常,凡是拋出異常的方法均可以經過try-catch進行捕獲,固然運行時異常能夠不捕獲。一個方法聲明拋出什麼樣的異常須要查詢API文檔。
每一個try代碼塊能夠伴隨一個或多個catch代碼塊,用於處理try代碼塊中所可能發生的多種異常。catch(Throwable e)語句中的e是捕獲異常對象,e必須是Throwable的子類,異常對象e的做用域在該catch代碼塊中。
下面看看一個try-catch示例:
//HelloWorld.java文件 package com.a51work6; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class HelloWorld { public static void main(String[] args) { Date date = readDate(); System.out.println("日期 = " + date); } // 解析日期 public static Date readDate() { ① try { String str = "2018-8-18"; //"201A-18-18" DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); // 從字符串中解析日期 Date date = df.parse(str); ② return date; } catch (ParseException e) { ③ System.out.println("處理ParseException…"); e.printStackTrace(); ④ } return null; } }
上述代碼第①行定義了一個靜態方法用來將字符串解析成日期,但並不是全部的字符串都是有效的日期字符串,所以調用代碼第②行的解析方法parse()有可能發生ParseException異常,ParseException是受檢查異常,在本例中使用try-catch捕獲。代碼第③行的e就是ParseException對象。代碼第④行e.printStackTrace()是打印異常堆棧跟蹤信息,本例中的"2018-8-18"字符串是有個有效的日期字符串,所以不會發生異常。若是將字符串改成無效的日期字符串,如"201A-18-18",則會打印信息。
處理ParseException java.text.ParseException: Unparseable date: "201A-18-18" 日期 = null at java.text.DateFormat.parse(Unknown Source) at com.a51work6.HelloWorld.readDate(HelloWorld.java:24) at com.a51work6.HelloWorld.main(HelloWorld.java:13)
提示 在捕獲到異常以後,經過e.printStackTrace()語句打印異常堆棧跟蹤信息,每每只是用於調試,給程序員提示信息。堆棧跟蹤信息對最終用戶是沒有意義的,本例中若是出現異常頗有多是用戶輸入的日期無效,捕獲到異常以後給用戶彈出一個對話框,提示用戶輸入日期無效,請用戶從新輸入,用戶從新輸入後再從新調用上述方法。這纔是捕獲異常以後的正確處理方案。
若是try代碼塊中有不少語句會發生異常,並且發生的異常種類又不少。那麼能夠在try後面跟有多個catch代碼塊。多catch代碼塊語法以下:
try{ //可能會發生異常的語句 } catch(Throwable e){ //處理異常e } catch(Throwable e){ //處理異常e } catch(Throwable e){ //處理異常e }
在多個catch代碼狀況下,當一個catch代碼塊捕獲到一個異常時,其餘的catch代碼塊就再也不進行匹配。
注意 當捕獲的多個異常類之間存在父子關係時,捕獲異常順序與catch代碼塊的順序有關。通常先捕獲子類,後捕獲父類,不然子類捕獲不到。
示例代碼以下:
//HelloWorld.java文件 package com.a51work6; …… public class HelloWorld { public static void main(String[] args) { Date date = readDate(); System.out.println("讀取的日期 = " + date); } public static Date readDate() { FileInputStream readfile = null; InputStreamReader ir = null; BufferedReader in = null; try { readfile = new FileInputStream("readme.txt"); ① ir = new InputStreamReader(readfile); in = new BufferedReader(ir); // 讀取文件中的一行數據 String str = in.readLine(); ② if (str == null) { return null; } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = df.parse(str); ③ return date; } catch (FileNotFoundException e) { ④ System.out.println("處理FileNotFoundException..."); e.printStackTrace(); } catch (IOException e) { ⑤ System.out.println("處理IOException..."); e.printStackTrace(); } catch (ParseException e) { ⑥ System.out.println("處理ParseException..."); e.printStackTrace(); } return null; } }
上述代碼經過Java I/O(輸入輸出)流技術從文件readme.txt中讀取字符串,而後解析成爲日期。因爲Java I/O技術尚未介紹,讀者先不要關注I/O技術細節,這考慮調用它們的方法會發生異常就能夠了。
在try代碼塊中第①行代碼調用FileInputStream構造方法能夠會發生FileNotFoundException異常。第②行代碼調用BufferedReader輸入流的readLine()方法能夠會發生IOException異常。從圖14-1可見FileNotFoundException異常是IOException異常的子類,應該先FileNotFoundException捕獲,見代碼第④行;後捕獲IOException,見代碼第⑤行。
若是將FileNotFoundException和IOException捕獲順序調換,代碼以下:
try{ //可能會發生異常的語句 } catch (IOException e) { // IOException異常處理 } catch (FileNotFoundException e) { // FileNotFoundException異常處理 }
那麼第二個catch代碼塊永遠不會進入,FileNotFoundException異常處理永遠不會執行。
因爲上述代碼第⑥行ParseException異常與IOException和FileNotFoundException異常沒有父子關係,捕獲ParseException異常位置能夠隨意放置。
Java提供的try-catch語句嵌套是能夠任意嵌套,修改14.3.2節示例代碼以下:
//HelloWorld.java文件 package com.a51work6; … … public class HelloWorld { public static void main(String[] args) { Date date = readDate(); System.out.println("讀取的日期 = " + date); } public static Date readDate() { FileInputStream readfile = null; InputStreamReader ir = null; BufferedReader in = null; try { readfile = new FileInputStream("readme.txt"); ir = new InputStreamReader(readfile); in = new BufferedReader(ir); try { ① String str = in.readLine(); ② if (str == null) { return null; } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = df.parse(str); ③ return date; } catch (ParseException e) { System.out.println("處理ParseException..."); e.printStackTrace(); } ④ } catch (FileNotFoundException e) { ⑤ System.out.println("處理FileNotFoundException..."); e.printStackTrace(); } catch (IOException e) { ⑥ System.out.println("處理IOException..."); e.printStackTrace(); } return null; } }
上述代碼第①~④行是捕獲ParseException異常try-catch語句,可見這個try-catch語句就是嵌套在捕獲IOException和FileNotFoundException異常的try-catch語句中。
程序執行時內層若是會發生異常,首先由內層catch進行捕獲,若是捕獲不到,則由外層catch捕獲。例如:代碼第②行的readLine()方法可能發生IOException異常,該異常沒法被內層catch捕獲,最後被代碼第⑥行的外層catch捕獲。
注意 try-catch不只能夠嵌套在try代碼塊中,還能夠嵌套在catch代碼塊或finally代碼塊,finally代碼塊後面會詳細介紹。try-catch嵌套會使程序流程變的複雜,若是能用多catch捕獲的異常,儘可能不要使用try-catch嵌套。特別對於初學者不要簡單地使用Eclipse的語法提示不加區分地添加try-catch嵌套,要梳理好程序的流程再考慮try-catch嵌套的必要性。
多catch代碼塊客觀上提升了程序的健壯性,可是程序代碼量大大增長。若是有些異常雖然種類不一樣,但捕獲以後的處理是相同的,看以下代碼。
try{ //可能會發生異常的語句 } catch (FileNotFoundException e) { //調用方法methodA處理 } catch (IOException e) { //調用方法methodA處理 } catch (ParseException e) { //調用方法methodA處理 }
三個不一樣類型的異常,要求捕獲以後的處理都是調用methodA方法。是否能夠把這些異常合併處理,Java 7推出了多重捕獲(multi-catch)技術,能夠幫助解決此類問題,上述代碼修改以下:
try{ //可能會發生異常的語句 } catch (IOException | ParseException e) { //調用方法methodA處理 }
在catch中多重捕獲異經常使用「|」運算符鏈接起來。
注意 有的讀者會問什麼不寫成FileNotFoundException | IOException | ParseException 呢?這是由於因爲FileNotFoundException屬於IOException異常,IOException異常能夠捕獲它的全部子類異常了。
http://edu.51cto.com/topic/1246.html
http://www.zhijieketang.com/group/5