淺談Java異常

前言

本文基於筆者自身的學習程度來淺談一下Java的異常及其通常的處理方法。什麼是異常?java

Java異常的分類和結構圖

Java程序運行時,發生的不被指望的事件,它阻止了程序按照程序員的預期正常執行,這就是異常。程序員

1.Throwable

Java標準庫內建了一些通用的異常,這些類以Throwable爲頂層父類。全部的異常都是從Throwable繼承而來的,是全部異常的共同祖先。數據庫

2.Throwable有兩個子類,Error和Exception。

1.Error(錯誤)

Error類以及他的子類的實例,表明了JVM自己的錯誤。錯誤不能被程序員經過代碼處理,Error不多出現。所以,程序員應該關注Exception爲父類的分支下的各類異常類。編程

2.Exception(異常)

Exception以及他的子類,表明程序運行時發送的各類不指望發生的事件。能夠被Java異常處理機制使用,是異常處理的核心。數組

基於Java對異常的處理要求的分類。

1.Unckecked exception

非檢查異常。Error和RuntimeException以及他們的子類。javac在編譯時,不會提示和發現這樣的異常,不要求在程序處理這些異常。因此若是願意,咱們能夠編寫代碼處理(使用try…catch…finally)這樣的異常,也能夠不處理。對於這些異常,咱們應該修正代碼,而不是去經過異常處理器處理。這樣的異常發生的緣由多半是代碼寫的有問題。如除0錯誤ArithmeticException,錯誤的強制類型轉換錯誤ClassCastException,數組索引越界ArrayIndexOutOfBoundsException,使用了空對象NullPointerException等等。bash

2.Checked Exception

檢查異常。除了Error 和 RuntimeException的其它異常。可檢查的異常,這是編碼時很是經常使用的,全部checked exception都是須要在代碼中處理的。它們的發生是能夠預測的,正常的一種狀況,能夠合理的處理。java強制要求程序員爲這樣的異常作預備處理工做(使用try…catch…finally或者throws)。在方法中要麼用try-catch語句捕獲它並處理,要麼用throws子句聲明拋出它,不然編譯不會經過。這樣的異常通常是由程序的運行環境致使的。由於程序可能被運行在各類未知的環境下,而程序員沒法干預用戶如何使用他編寫的程序,因而程序員就應該爲這樣的異常時刻準備着。如SQLException , IOException,ClassNotFoundException 等。多線程

結構圖

異常的處理

在編寫代碼處理異常時,對於檢查異常,有2種不一樣的處理方式:使用try…catch…finally語句塊處理它。或者,在函數簽名中使用throws 聲明交給函數調用者caller去解決。模塊化

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主要作一些清理工做,如流的關閉,數據庫鏈接的關閉等。
    
}

複製代碼

須要注意的地方

一、try塊中的局部變量和catch塊中的局部變量(包括異常變量),以及finally中的局部變量,他們之間不可共享使用。函數

二、每個catch塊用於處理一個異常。異常匹配是按照catch塊的順序從上往下尋找的,只有第一個匹配的catch會獲得執行。匹配時,不只運行精確匹配,也支持父類匹配,所以,若是同一個try塊下的多個catch異常類型有父子關係,應該將子類異常放在前面,父類異常放在後面,這樣保證每一個catch塊都有存在的意義。學習

三、java中,異常處理的任務就是將執行控制流從異常發生的地方轉移到可以處理這種異常的地方去。也就是說:當一個函數的某條語句發生異常時,這條語句的後面的語句不會再執行,它失去了焦點。執行流跳轉到最近的匹配的異常處理catch代碼塊去執行,異常被處理完後,執行流會接着在「處理了這個異常的catch代碼塊」後面接着執行。

public static void main(String[] args){
       int a=8/0;  //異常拋出點      
      System.out.println("處理異常");
      
	}

複製代碼

public static void main(String[] args){
     try {
        int a=8/0;  //異常拋出點
    }catch(ArithmeticException e) {
        System.out.println("處理異常");
    }
}

複製代碼

finally塊

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塊。

throws 函數聲明

throws聲明:若是一個方法內部的代碼會拋出檢查異常(checked exception),而方法本身又沒有徹底處理掉,則java保證你必須在方法的簽名上使用throws關鍵字聲明這些可能拋出的異常,不然編譯不經過。

throws是另外一種處理異常的方式,它不一樣於try…catch…finally,throws僅僅是將函數中可能出現的異常向調用者聲明,而本身則不具體處理。

採起這種異常處理的緣由多是:方法自己不知道如何處理這樣的異常,或者說讓調用者處理更好,調用者須要爲可能發生的異常負責。

public void throwBibi() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{ 
     /*throwBibi內部能夠拋出 ExceptionType1 , ExceptionType2 ,
     ExceptionTypeN 類的異常,或者他們的子類的異常對象。*/
}

複製代碼

注意事項 一、若是是非檢查異常(unchecked exception),即Error、RuntimeException或它們的子類,那麼能夠不使用Throws關鍵字來聲明要拋出的異常,編譯仍能順利經過,但在運行時會被系統拋出。

二、當拋出了異常,則該方法的調用者必須處理或者從新拋出該異常。

三、當子類重寫父類拋出異常的方法時,聲明的異常必須是父類方法所聲明異常的同類或子類。

throw 異常拋出語句 throw exceptionObject

程序員也能夠經過throw語句手動顯式的拋出一個異常。throw語句的後面必須是一個異常對象。

throw 語句必須寫在函數中,執行throw語句的地方就是一個異常拋出點,它和由JRE自動造成的異常拋出點沒有任何差異。

public void save(User user)
{
      if(user  == null) 
          throw new IllegalArgumentException("User對象爲空");
      //......
 
}

複製代碼

throw與throws的區別

一、throw用在方法體內,上面代碼顯示了,是直接在方法體內。 throws用在方法聲明後面,表示再拋出異常,由該方法的調用者來處理。

二、throw是具體向外拋異常的,拋出的是一個異常實例。 throws聲明瞭是哪一種類型的異常,使它的調用者能夠捕獲這個異常。

三、throw,若是執行了,那麼必定是拋出了某種異常了,但throws表示可能出現,但不必定。

四、同時出現的時候,throws出如今函數頭、throw出如今函數體,兩種不會由函數去處理,真正的處理由函數的上層調用處理。

自定義異常

所謂自定義異常,就是定義一個類,去繼承Throwable類或者它的子類。

使用Java內置的異常類能夠描述在編程時出現的大部分異常狀況,也能夠經過自定義異常描述特定業務產生的異常類型。

若是要自定義異常類,則擴展Exception類便可,所以這樣的自定義異常都屬於檢查異常(checked 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聲明的函數時,其throws聲明的異常必須在父類異常的可控範圍內——用於處理父類的throws方法的異常處理器,必須也適用於子類的這個帶throws方法 。這是爲了支持多態。

例如,父類方法throws 的是2個異常,子類就不能throws 3個及以上的異常。父類throws IOException,子類就必須throws IOException或者IOException的子類。

二、Java程序能夠是多線程的。每個線程都是一個獨立的執行流,獨立的函數調用棧。若是程序只有一個線程,那麼沒有被任何代碼處理的異常會致使程序終止。若是是多線程的,那麼沒有被任何代碼處理的異常僅僅會致使異常所在的線程結束。

也就是說,Java中的異常是線程獨立的,線程的問題應該由線程本身來解決,而不要委託到外部,也不會直接影響到其它線程的執行。

異常的鏈化

在一些大型的,模塊化的軟件開發中,有時咱們會捕獲到一個異常後再拋出另外一個異常。這樣將異常的發生緣由一個傳一個串起來,即把底層的異常信息傳給上層,這樣逐層拋出,稱之爲異常鏈化。

異常鏈化:以一個異常對象爲參數構造新的異常對象。新的異對象將包含先前異常的信息。這項技術主要是異常類的一個帶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;
}

複製代碼

結果

總結

在程序運行過程當中,意外發生的狀況,背離咱們程序自己的意圖的表現,均可以理解爲異常。

Java提供的異常機制能夠更好的提高程序的健壯性,是一Java學習過程當中必須掌握的一項基礎。

只是淺談,可能存在缺漏或錯誤的地方,望諒解。

願君莫禿了頭

相關文章
相關標籤/搜索