Java標準褲內建了一些通用的異常,這些類以Throwable爲頂層父類。java
Throwable又派生出Error類和Exception類。程序員
錯誤:Error類以及他的子類的實例,表明了JVM自己的錯誤。錯誤不能被程序員經過代碼處理,Error不多出現。所以,程序員應該關注Exception爲父類的分支下的各類異常類。數據庫
異常:Exception以及他的子類,表明程序運行時發送的各類不指望發生的事件。能夠被Java異常處理機制使用,是異常處理的核心。編程
整體上咱們根據Javac對異常的處理要求,將異常類分爲2類。數組
非檢查異常(unckecked exception):Error 和 RuntimeException 以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常。因此若是願意,咱們能夠編寫代碼處理(使用try...catch...finally)這樣的異常,也能夠不處理。對於這些異常,咱們應該修正代碼,而不是去經過異常處理器處理 。這樣的異常發生的緣由多半是代碼寫的有問題。如除0錯誤ArithmeticException,錯誤的強制類型轉換錯誤ClassCastException,數組索引越界ArrayIndexOutOfBoundsException,使用了空對象NullPointerException等等。多線程
檢查異常(checked exception):除了Error 和 RuntimeException的其它異常。javac強制要求程序員爲這樣的異常作預備處理工做(使用try...catch...finally或者throws)。在方法中要麼用try-catch語句捕獲它並處理,要麼用throws子句聲明拋出它,不然編譯不會經過。這樣的異常通常是由程序的運行環境致使的。由於程序可能被運行在各類未知的環境下,而程序員沒法干預用戶如何使用他編寫的程序,因而程序員就應該爲這樣的異常時刻準備着。如SQLException , IOException,ClassNotFoundException 等。編程語言
須要明確的是:檢查和非檢查是對於javac來講的,這樣就很好理解和區分了。ide
下面的代碼會演示2個異常類型:ArithmeticException 和 InputMismatchException。前者因爲整數除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 ; } } /***************************************** ----歡迎使用命令行除法計算器---- 2 0 Exception in thread "main" java.lang.ArithmeticException : / by zero at com.example.AllDemo.devide( AllDemo.java:30 ) at com.example.AllDemo.CMDCalculate( AllDemo.java:22 ) at com.example.AllDemo.main( AllDemo.java:12 ) ----歡迎使用命令行除法計算器---- 1 r Exception in thread "main" java.util.InputMismatchException at 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函數將拋出ArithmeticException異常,所以調用他的CMDCalculate函數也沒法正常完成,所以也發送異常,而CMDCalculate的caller——main 由於CMDCalculate拋出異常,也發生了異常,這樣一直向調用棧的棧底回溯。這種行爲叫作異常的冒泡,異常的冒泡是爲了在當前發生異常的函數或者這個函數的caller中找到最近的異常處理程序。因爲這個例子中沒有使用任何異常處理機制,所以異常最終由main函數拋給JRE,致使程序終止。
上面的代碼不使用異常處理機制,也能夠順利編譯,由於2個異常都是非檢查異常。可是下面的例子就必須使用異常處理機制,由於異常是檢查異常。
代碼中我選擇使用throws聲明異常,讓函數的調用者去處理可能發生的異常。可是爲何只throws了IOException呢?由於FileNotFoundException是IOException的子類,在處理範圍內。
@Test public void testException() throws IOException { //FileInputStream的構造函數會拋出FileNotFoundException FileInputStream fileIn = new FileInputStream("E:\\a.txt"); int word; //read方法會拋出IOException while((word = fileIn.read())!=-1) { System.out.print((char)word); } //close方法會拋出IOException fileIn.clos
}
try...catch...finally語句塊
try{ //try塊中放可能發生異常的代碼。
//若是執行完try且不發生異常,則接着去執行finally塊和finally後面的代碼(若是有的話)。
//若是發生異常,則嘗試去匹配catch塊。 }catch(SQLException SQLexception){ //每個catch塊用於捕獲並處理一個特定的異常,或者這異常類型的子類。Java7中能夠將多個異常聲明在一個catch中。 //catch後面的括號定義了異常類型和異常參數。若是異常與之匹配且是最早匹配到的,則虛擬機將使用這個catch塊來處理異常。 //在catch塊中可使用這個塊的異常參數來獲取異常的相關信息。異常參數是這個catch塊中的局部變量,其它塊不能訪問。 //若是當前try塊中發生的異常在後續的全部catch中都沒捕獲到,則先去執行finally,而後到這個函數的外部caller中去匹配異常處理器。
//若是try中沒有發生異常,則全部的catch塊將被忽略。
}catch(Exception exception){ //... }finally{
//finally塊一般是可選的。
//不管異常是否發生,異常是否匹配被處理,finally都會執行。 //一個try至少要有一個catch塊,不然, 至少要有1個finally塊。可是finally不是用來處理異常的,finally不會捕獲異常。 //finally主要作一些清理工做,如流的關閉,數據庫鏈接的關閉等。 }
須要注意的地方
public static void main(String[] args){ try { foo(); }catch(ArithmeticException ae) { System.out.println("處理異常"); } } public static void foo(){ int a = 5/0; //異常拋出點 System.out.println("爲何還不給我漲工資!!!"); //////////////////////不會執行 }
throws 函數聲明
throws聲明:若是一個方法內部的代碼會拋出檢查異常(checked exception),而方法本身又沒有徹底處理掉,則javac保證你必須在方法的簽名上使用throws關鍵字聲明這些可能拋出的異常,不然編譯不經過。
throws是另外一種處理異常的方式,它不一樣於try...catch...finally,throws僅僅是將函數中可能出現的異常向調用者聲明,而本身則不具體處理。
採起這種異常處理的緣由多是:方法自己不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者須要爲可能發生的異常負責。
public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN { //foo內部能夠拋出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 類的異常,或者他們的子類的異常對象。 }
finally塊無論異常是否發生,只要對應的try執行了,則它必定也執行。只有一種方法讓finally塊不執行:System.exit()。所以finally塊一般用來作資源釋放操做:關閉文件,關閉數據庫鏈接等等。
良好的編程習慣是:在try塊中打開資源,在finally塊中清理釋放這些資源。
須要注意的地方:
一、finally塊沒有處理異常的能力。處理異常的只能是catch塊。
二、在同一try...catch...finally塊中 ,若是try中拋出異常,且有匹配的catch塊,則先執行catch塊,再執行finally塊。若是沒有catch塊匹配,則先執行finally,而後去外面的調用者中尋找合適的catch塊。
三、在同一try...catch...finally塊中 ,try發生異常,且匹配的catch塊中處理異常時也拋出異常,那麼後面的finally也會執行:首先執行finally塊,而後去外圍調用者中尋找合適的catch塊。
這是正常的狀況,可是也有特例。關於finally有不少噁心,偏、怪、難的問題,我在本文最後統一介紹了,電梯速達->:finally塊和return
throw exceptionObject
程序員也能夠經過throw語句手動顯式的拋出一個異常。throw語句的後面必須是一個異常對象。
throw 語句必須寫在函數中,執行throw 語句的地方就是一個異常拋出點,它和由JRE自動造成的異常拋出點沒有任何差異。
public void save(User user) { if(user == null) throw new IllegalArgumentException("User對象爲空"); //...... }
在一些大型的,模塊化的軟件開發中,一旦一個地方發生異常,則如骨牌效應同樣,將致使一連串的異常。假設B模塊完成本身的邏輯須要調用A模塊的方法,若是A模塊發生異常,則B也將不能完成而發生異常,可是B在拋出異常時,會將A的異常信息掩蓋掉,這將使得異常的根源信息丟失。異常的鏈化能夠將多個模塊的異常串聯起來,使得異常信息不會丟失。
異常鏈化:以一個異常對象爲參數構造新的異常對象。新的異對象將包含先前異常的信息。這項技術主要是異常類的一個帶Throwable參數的函數來實現的。這個當作參數的異常,咱們叫他根源異常(cause)。
查看Throwable類源碼,能夠發現裏面有一個Throwable字段cause,就是它保存了構造時傳遞的根源異常參數。這種設計和鏈表的結點類設計一模一樣,所以造成鏈也是天然的了。
public class Throwable implements Serializable { private Throwable cause = this; public Throwable(String message, Throwable cause) { fillInStackTrace(); detailMessage = message; this.cause = cause; } public Throwable(Throwable cause) { fillInStackTrace(); detailMessage = (cause==null ? null : cause.toString()); this.cause = cause; } //........ }
下面是一個例子,演示了異常的鏈化:從命令行輸入2個int,將他們相加,輸出。輸入的數不是int,則致使getInputNumbers異常,從而致使add函數異常,則能夠在add函數中拋出
一個鏈化的異常。
public static void main(String[] args) { System.out.println("請輸入2個加數"); int result; try { result = add(); System.out.println("結果:"+result); } catch (Exception e){ e.printStackTrace(); } } //獲取輸入的2個整數返回 private static List<Integer> getInputNumbers() { List<Integer> nums = new ArrayList<>(); Scanner scan = new Scanner(System.in); try { int num1 = scan.nextInt(); int num2 = scan.nextInt(); nums.add(new Integer(num1)); nums.add(new Integer(num2)); }catch(InputMismatchException immExp){ throw immExp; }finally { scan.close(); } return nums; } //執行加法計算 private static int add() throws Exception { int result; try { List<Integer> nums =getInputNumbers(); result = nums.get(0) + nums.get(1); }catch(InputMismatchException immExp){ throw new Exception("計算失敗",immExp); /////////////////////////////鏈化:以一個異常對象爲參數構造新的異常對象。 } return result; }
/*
請輸入2個加數
r 1
java.lang.Exception: 計算失敗
at practise.ExceptionTest.add(ExceptionTest.java:53)
at practise.ExceptionTest.main(ExceptionTest.java:18)
Caused by: java.util.InputMismatchException
at 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 practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
at practise.ExceptionTest.add(ExceptionTest.java:48)
... 1 more
*/
若是要自定義異常類,則擴展Exception類便可,所以這樣的自定義異常都屬於檢查異常(checked exception)。若是要自定義非檢查異常,則擴展自RuntimeException。
按照國際慣例,自定義的異常應該老是包含以下的構造函數:
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聲明的函數時,其throws聲明的異常必須在父類異常的可控範圍內——用於處理父類的throws方法的異常處理器,必須也適用於子類的這個帶throws方法 。這是爲了支持多態。
例如,父類方法throws 的是2個異常,子類就不能throws 3個及以上的異常。父類throws IOException,子類就必須throws IOException或者IOException的子類。
至於爲何?我想,也許下面的例子能夠說明。
class Father { public void start() throws IOException { throw new IOException(); } } class Son extends Father { public void start() throws Exception { throw new SQLException(); } } /**********************假設上面的代碼是容許的(實質是錯誤的)***********************/ class Test { public static void main(String[] args) { Father[] objs = new Father[2]; objs[0] = new Father(); objs[1] = new Son(); for(Father obj:objs) { //由於Son類拋出的實質是SQLException,而IOException沒法處理它。 //那麼這裏的try。。catch就不能處理Son中的異常。 //多態就不能實現了。 try { obj.start(); }catch(IOException) { //處理IOException } } } }
二、Java程序能夠是多線程的。每個線程都是一個獨立的執行流,獨立的函數調用棧。若是程序只有一個線程,那麼沒有被任何代碼處理的異常 會致使程序終止。若是是多線程的,那麼沒有被任何代碼處理的異常僅僅會致使異常所在的線程結束。
首先一個不容易理解的事實:在 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 5 */
不少人面對這個問題時,老是在概括執行的順序和規律,不過我以爲仍是很難理解。我本身總結了一個方法。用以下GIF圖說明。
也就是說:try...catch...finally中的return 只要能執行,就都執行了,他們共同向同一個內存地址(假設地址是0x80)寫入返回值,後執行的將覆蓋先執行的數據,而真正被調用者取的返回值就是最後一次寫入的。那麼,按照這個思想,下面的這個例子也就不難理解了。
public static void main(String[] args) { int result; result = foo(); System.out.println(result); /////////2 result = bar(); System.out.println(result); /////////2 } @SuppressWarnings("finally") public static int foo() { trz{ int a = 5 / 0; } catch (Exception e){ return 1; } finally{ return 2; } } @SuppressWarnings("finally") public static int bar() { try { return 1; }finally { return 2; } }
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; } } }
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個例子都異於常人的編碼思惟,所以我建議: