java異常處理之try...catch...finally詳解

    異常處理機制已經成爲判斷一門編程語言是否成熟的標準之一,其對代碼的健壯性有很大影響。一直以來異常處理使用不是很得心應手,今天對異常進行了較爲深入的學習,這篇主要是對try…catch…finally的一個總結。

一.java繼承體系

    Java語言爲異常處理提供了豐富的異常類,這些類之間有嚴格的繼承關係。如圖:
這裏寫圖片描述
從圖中我們可以看出,所有的類都是繼承於Throwable這個父類,java將所有的非正常情況分爲兩種:Error(錯誤)和Exception(異常),Error錯誤一般是於虛擬機相關的問題,如系統崩潰、虛擬機錯誤、動態鏈接失敗等,這種錯誤是無法恢復或不可能捕獲的,而我們能處理的是Exception類下的錯誤。Exception則分爲兩大類,RuntimeException(運行時異常)和其他異常(Checked異常),其他異常(Checked異常)是各種形式的編譯錯誤,是我們必須顯示處理纔可以通過變異的;而運行時錯誤顧名思義就是程序已經通過了編譯,在運行時出現的錯誤,若是對這些異常置之不理會導致程序停止運行、佔用資源無法釋放甚至導致系統崩潰。

二.java異常處理機制及實現方法

  1. 主要依賴於try、catch、finally、throw和throws這五個關鍵字。(throw和throws本篇不涉及)
  2. try…catch…finally處理機制:try關鍵字後跟一個花括號栝起的代碼塊(即使該代碼塊只有一行也不能省略花括號),簡稱try塊。catch對應異常類型和代碼塊,用於表明更改catch塊用於處理該種類型的異常。一個try塊後可以跟多個catch塊。在catch塊後還可以跟一個finally塊,finally塊用於回收在try塊裏打開的資源。
    這樣講過於抽象,那我們看幾個例子:
    e.g.1 try…catch語句塊
/*功能:對輸入的兩個數進行相除運算*/
public class DivTest {
    public static void main(String[] args) {
        try {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a/b;
            System.out.println("您輸入的兩個數相除的結果是:" + c);
        } catch(IndexOutOfBoundsException ie) {
            System.out.println("數組越界");
        } catch(NumberFormatException ne) {
            System.out.println("數字格式異常");
        }  catch(ArithmeticException ae) {
            System.out.println("算術異常");
        }  catch(Exception e) {
            System.out.println("未知異常");
        } 
    }
}

    以上代碼我們看到,對不同的異常情況作了不同的處理:輸入參數不夠會發生數組越界異常、輸入參數不是數字發生數字格式異常、若輸入第二個數是0,則發生除0異常,調用算術異常進行處理、出現其他異常時那麼該異常對象必定是Exception類或其子類的實例,java調用Exception類對其進行處理,前三種異常類均是RuntimeException的子類。在使用try…catch語句塊時需要知道或注意以下幾點:
 1) 處理過程:代碼在執行的時候,進入try塊,若是在try塊中出現了異常,系統會自動生成一個一場對象,該對象被提交給java運行時環境,這就是異常的拋出;在java運行時環境收到異常對象時則把該對象交給catch塊處理,這個過程叫做異常的捕獲;若找到相應的catch塊就執行catch塊中的代碼,若沒有找到,則運行時環境終止,程序也退出。
 2) 執行一次try塊只執行一個catch塊
 3) 有多個catch塊並有繼承關係的情況下必須先寫子類後寫父類(即先捕獲小異常再捕獲大異常),若寫反在編譯時就會報錯
 4) Java7提供的多異常捕獲:在Java7之前,每一個catch塊只能捕獲一種異常,但從java7開始,一個catch塊可以捕獲多種類型的異常。在使用多異常捕獲應注意兩點:
  (1) 多種異常之間用豎線( | )隔開
  (2) 多種異常對象被final隱式修飾,因此程序不能對其重新賦值
 以下代碼是多異常捕獲的例子:
 e.g.2

/*多異常捕獲*/
public class MultiExceptionTest {
    public static void main(String[] args) {
        try {
            int a = Integer.parseInt(args[0]);
            int b = Integer.parseInt(args[1]);
            int c = a/b;
            System.out.println("您輸入的兩個數相除的結果是:" + c);
        } catch(IndexOutOfBoundsException|NumberFormatException|
        ArithmeticException ie) {
            System.out.println("數組越界或數字格式異常或算術異常");
            ie = new ArithmeticExcrption("test");   //①
        } catch(Exception e) {
            System.out.println("未知異常");
            e = new RuntimeException("test");   //②
        } 
    }
}

可以看出,以上代碼中,①號代碼是錯誤的,因爲ie是被final隱式修飾的對象,②號代碼是正確的
  3. 使用finally回收資源:有些時候我們在try塊中打開了一些物理資源(例如數據庫鏈接、網絡連接和磁盤文件等),這些資源都應進行顯示回收。有人說java不是有垃圾回收機制嗎?java的垃圾回收機制是自動回收堆內存中對象所佔用的內存,而物理資源是不會自動回收的。
finally重點學習以下幾點:
 1) 執行過程以及引入finally的原因:finally最後執行並且最後執行,物理資源回收放在finally塊中的原因就是finally塊一定會被執行。相反,若是放在try塊中,在執行之前就出現異常則跳轉至catch塊中,則回收資源的代碼不會被執行;同樣的,若是放在catch塊中,若不發生異常,那麼catch塊就不會被執行
 2) 若是在catch快中有return語句,則先執行完finally中的程序後再回到catch塊中並執行return語句
 3) 若是在finally中有return語句,那麼try塊和catch塊中的return語句都會失效,不會被執行
 4) 若在catch塊中強制退出虛擬機,如使用System.exit(1)語句,則會直接退出程序,finally也不會得到執行
  e.g.3

/*該類功能:打開a.txt文件,在finally塊中對資源進行回收*/
/* 對代碼中一些方法的解釋:
 * 所有異常都包含以下幾種訪問異常信息的常用方法:
 * getMessage():返回該異常的詳細描述字符串
 * printStackTrace():將該異常的跟蹤棧信息輸出到標準錯誤輸出
 * printStaceTrace(PrintStack s):將該異常的跟蹤棧信息到執行輸出流
 * getStackTrace():返回該異常的跟蹤棧信息
 */
public class FinallyTest {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("a.txt");
        }catch(IOException ioe) {
            System.out.println(ioe.getMessage());
            return;           //①
            System.exit(1);   //②
        }finally {
            if(fis != null) {
                try{
                    fis.close();
                }catch(IOException ioe) {
                    ioe.printStackTrace();
                }
            }
            System.out.println("執行finally塊裏的資源回收!");
        }
    }
}

註釋掉②號代碼運行以上程序,我們看到的結果是:

a.txt (系統找不到該文件。)
程序已經執行了finally裏的資源回收!

註釋掉①號代碼運行以上程序,我們看到的結果是:

a.txt (系統找不到該文件。)

  4. 嵌套
例如e.g.3代碼所示,finally塊中還嵌套了一個try…catch語句塊,這種在try塊、catch塊或finally塊中包含完整的異常處理流程的情形被稱爲異常的嵌套。一般對嵌套深度沒有限制,但是層次太深的嵌套會降低可讀性。
  5.Java7的自動關閉資源的try語句:
  在java7之前,我們必須像e.g.3中的代碼一樣手動關閉文件,回收資源。在Java7中增強了try語句的功能,它允許在try關鍵字後緊跟一對圓括號,圓括號可以聲明、初始化一個或多個資源,此處的資源指的是那些必須在程序結束時顯示關閉的資源,try語句在該語句結束時自動關閉這些資源。這些資源實現類必須實現AutoCloseable或Closeable接口,實現這兩個接口就必須實現close()方法。
注:Closeable是AutoCloseable接口的子接口,Closeable接口裏的close()方法聲明拋出了IOException,因此它的實現類在實現close()方法時只能聲明拋出IOException或其子類;AutoCloseable接口裏的close()方法聲明拋出了Exception,因此它的實現類在實現close()方法時能拋出任何異常。Java7幾乎把所有的“資源類”(包括文件IO的各種類、JDBC編程的Connection、Statement等接口)進行了改寫,改寫後的資源類都實現了AutoCloseable或Closeable接口
  e.g.4

/*使用自動回收資源的try語句*/
public class AutoCloseTest {
    public static void main(String[] args) throws IOException {
        try(
        //聲明、初始化兩個可關閉的資源,try語句會自動關閉這兩個資源
        BufferedReader br = new BufferedReader(
        new FileReader("AutoCloseTest.java"));
        PrintStream ps =  new PrintStream(
        new FileOutputStream("a.txt"))) {
            //使用兩個資源
            System.out.println(br.readLine());
            ps.println("自動關閉資源的try語句")
        }
    }
}

以上try語句塊後的圓括號中聲明、初始化了兩個IO流,由於BufferedReader、PrintStream都實現了Closeable接口,所以try語句會自動關閉它們。自動關閉資源的try語句塊相當於包含了隱式的finally塊用於關閉資源,這個try語句可以沒有catch塊也可以沒有finally塊,大大減少了代碼的長度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章