Java™ 教程(捕獲和處理異常)

捕獲和處理異常

本節描述如何使用三個異常處理程序組件 — trycatchfinally塊 — 來編寫異常處理程序,而後,解釋了Java SE 7中引入的try-with-resources語句,try-with-resources語句特別適用於使用Closeable資源的狀況,例如流。html

本節的最後一部分將介紹一個示例,並分析各類場景中發生的狀況。java

如下示例定義並實現名爲ListOfNumbers的類,構造時,ListOfNumbers建立一個ArrayList,其中包含10個具備順序值0到9的整數元素,ListOfNumbers類還定義了一個名爲writeList的方法,該方法將數字列表寫入名爲OutFile.txt的文本文件中,此示例使用java.io中定義的輸出類,這些類在基礎I/O中介紹。git

// Note: This class will not compile yet.
import java.io.*;
import java.util.List;
import java.util.ArrayList;

public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
    // The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

        for (int i = 0; i < SIZE; i++) {
            // The get(int) method throws IndexOutOfBoundsException, which must be caught.
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}

FileWriter構造函數初始化文件上的輸出流,若是沒法打開該文件,則構造函數將拋出IOException。對ArrayList類的get方法的調用,若是其參數的值過小(小於0)或太大(大於ArrayList當前包含的元素數),則拋出IndexOutOfBoundsException程序員

若是你嘗試編譯ListOfNumbers類,編譯器將輸出有關FileWriter構造函數拋出的異常的錯誤消息,可是,它不會顯示有關get拋出的異常的錯誤消息,緣由是構造函數拋出的異常IOException是一個通過檢查的異常,而get方法拋出的異常(IndexOutOfBoundsException)是一個未經檢查的異常。github

如今你已熟悉ListOfNumbers類以及能夠在其中拋出異常的位置,你已準備好編寫異常處理程序來捕獲和處理這些異常。sql

try塊

構造異常處理程序的第一步是使用try塊將可能引起異常的代碼括起來,一般,try塊以下所示:segmentfault

try {
    code
}
catch and finally blocks . . .

標記爲code的示例段中包含一個或多個可能引起異常的合法代碼行(catchfinally塊將在接下來的兩個小節中解釋)。api

要從ListOfNumbers類構造writeList方法的異常處理程序,請將writeList方法的異常拋出語句包含在try塊中,有不止一種方法能夠作到這一點,你能夠將可能引起異常的每行代碼放在其本身的try塊中,併爲每一個代碼提供單獨的異常處理程序。或者,你能夠將全部writeList代碼放在一個try塊中,並將多個處理程序與它相關聯,如下列表對整個方法使用一個try塊,由於所討論的代碼很是短。oracle

private List<Integer> list;
private static final int SIZE = 10;

public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entered try statement");
        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    }
    catch and finally blocks  . . .
}

若是try塊中發生異常,則該異常由與之關聯的異常處理程序處理,要將異常處理程序與try塊關聯,必須在其後面放置一個catch塊。函數

catch塊

經過在try塊以後直接提供一個或多個catch塊,能夠將異常處理程序與try塊關聯,try塊的末尾和第一個catch塊的開頭之間不能有代碼。

try {

} catch (ExceptionType name) {

} catch (ExceptionType name) {

}

每一個catch塊都是一個異常處理程序,它處理由其參數表示的異常類型,參數類型ExceptionType聲明瞭處理程序能夠處理的異常類型,而且必須是從Throwable類繼承的類的名稱,處理程序可使用name引用異常。

catch塊包含在調用異常處理程序時執行的代碼,當處理程序是調用堆棧中的第一個ExceptionType與拋出的異常類型匹配時,運行時系統調用此異常處理程序,若是拋出的對象能夠合法地分配給異常處理程序的參數,則系統認爲它是匹配的。

如下是writeList方法的兩個異常處理程序:

try {

} catch (IndexOutOfBoundsException e) {
    System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Caught IOException: " + e.getMessage());
}

異常處理程序不只能夠打印錯誤消息或中止程序,它們能夠執行錯誤恢復、提示用戶作出決定,或使用鏈式異常將錯誤傳播到更高級別的處理程序,如「鏈式異常」部分所述。

使用一個異常處理程序捕獲多種類型的異常

在Java SE 7及更高版本中,單個catch塊能夠處理多種類型的異常,此功能能夠減小代碼重複並減小捕獲過於寬泛的異常的誘惑。

catch子句中,指定塊能夠處理的異常類型,並使用豎線(|)分隔每一個異常類型:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}
注意:若是 catch塊處理多個異常類型,則 catch參數隱式爲 final,在此示例中, catch參數 exfinal,所以你沒法在 catch塊中爲其分配任何值。

finally塊

try塊退出時,finally塊老是執行,這確保即便發生意外異常也會執行finally塊,但finally不只僅是異常處理有用 — 它容許程序員避免因returncontinuebreak而意外繞過清理代碼,將清理代碼放在finally塊中始終是一種很好的作法,即便沒有預期的異常狀況也是如此。

注意:若是在執行 trycatch代碼時JVM退出,則 finally塊可能沒法執行,一樣,若是執行 trycatch代碼的線程被中斷或終止,則即便應用程序做爲一個總體繼續, finally塊也可能沒法執行。

你在此處使用的writeList方法的try塊打開了PrintWriter,程序應該在退出writeList方法以前關閉該流,這帶來了一個有點複雜的問題,由於writeListtry塊能夠以三種方式之一退出。

  1. new FileWriter語句失敗並拋出IOException
  2. list.get(i)語句失敗並拋出IndexOutOfBoundsException
  3. 一切都成功,try塊正常退出。

不管try塊中發生了什麼,運行時系統老是執行finally塊中的語句,因此這是進行清理的最佳地點。

下面的writeList方法的finally塊清理而後關閉PrintWriter

finally {
    if (out != null) { 
        System.out.println("Closing PrintWriter");
        out.close(); 
    } else { 
        System.out.println("PrintWriter not open");
    } 
}
重要提示: finally塊是防止資源泄漏的關鍵工具,關閉文件或以其餘方式恢復資源時,將代碼放在 finally塊中以確保始終恢復資源。

請考慮在這些狀況下使用try-with-resources語句,這會在再也不須要時自動釋放系統資源,try-with-resources語句部分提供了更多信息。

try-with-resources語句

try-with-resources語句是一個聲明一個或多個資源的try語句,資源是在程序完成後必須關閉的對象,try-with-resources語句確保在語句結束時關閉每一個資源,實現java.lang.AutoCloseable的任何對象(包括實現java.io.Closeable的全部對象)均可以用做資源。

如下示例從文件中讀取第一行,它使用BufferedReader實例從文件中讀取數據,BufferedReader是一個在程序完成後必須關閉的資源:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

在此示例中,try-with-resources語句中聲明的資源是BufferedReader,聲明語句出如今try關鍵字後面的括號內,Java SE 7及更高版本中的BufferedReader類實現了java.lang.AutoCloseable接口,由於BufferedReader實例是在try-with-resource語句中聲明的,因此不管try語句是正常完成仍是忽然完成(因爲BufferedReader.readLine方法拋出IOException),它都將被關閉。

在Java SE 7以前,你可使用finally塊來確保關閉資源,不管try語句是正常仍是忽然完成,如下示例使用finally塊而不是try-with-resources語句:

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

可是,在此示例中,若是方法readLineclose都拋出異常,則方法readFirstLineFromFileWithFinallyBlock拋出finally塊拋出的異常,從try塊拋出的異常被抑制。相反,在示例readFirstLineFromFile中,若是從try塊和try-with-resources語句拋出異常,則readFirstLineFromFile方法拋出try塊拋出的異常,從try-with-resources塊拋出的異常被抑制,在Java SE 7及更高版本中,你能夠檢索已抑制的異常,有關詳細信息,請參閱「抑制的異常」部分。

你能夠在try-with-resources語句中聲明一個或多個資源,如下示例檢索zip文件zipFileName中打包的文件的名稱,並建立包含這些文件名稱的文本文件:

public static void writeToFileZipFileContents(String zipFileName,
                                           String outputFileName)
                                           throws java.io.IOException {

    java.nio.charset.Charset charset =
         java.nio.charset.StandardCharsets.US_ASCII;
    java.nio.file.Path outputFilePath =
         java.nio.file.Paths.get(outputFileName);

    // Open zip file and create output file with 
    // try-with-resources statement

    try (
        java.util.zip.ZipFile zf =
             new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = 
            java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        // Enumerate each entry
        for (java.util.Enumeration entries =
                                zf.entries(); entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName =
                 ((java.util.zip.ZipEntry)entries.nextElement()).getName() +
                 newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

在此示例中,try-with-resources語句包含兩個以分號分隔的聲明:ZipFileBufferedWriter,當直接跟隨它的代碼塊正常或因爲異常而終止時,將按此順序自動調用BufferedWriterZipFile對象的close方法,請注意,資源的close方法按其建立的相反順序調用。

如下示例使用try-with-resources語句自動關閉java.sql.Statement對象:

public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {
        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");

            System.out.println(coffeeName + ", " + supplierID + ", " + 
                               price + ", " + sales + ", " + total);
        }
    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}

此示例中使用的資源java.sql.Statement是JDBC 4.1及更高版本API的一部分。

注意: try-with-resources語句能夠像普通的 try語句同樣有 catchfinally塊,在 try-with-resources語句中,在聲明的資源關閉後運行任何 catchfinally塊。

抑制異常

能夠從與try-with-resources語句關聯的代碼塊中拋出異常,在示例writeToFileZipFileContents中,能夠從try塊拋出異常,當try-with-resources語句嘗試關閉ZipFileBufferedWriter對象時,最多能夠拋出兩個異常。若是從try塊拋出異常,而且從try-with-resources語句中拋出一個或多個異常,則會抑制從try-with-resources語句拋出的那些異常,而且塊拋出的異常是writeToFileZipFileContents方法拋出的異常,你能夠經過從try塊拋出的異常中調用Throwable.getSuppressed方法來檢索這些抑制的異常。

實現AutoCloseable或Closeable接口的類

請參閱AutoCloseableCloseable接口的Javadoc,以獲取實現這些接口之一的類列表,Closeable接口擴展了AutoCloseable接口。Closeable接口的close方法拋出IOException類型的異常,而AutoCloseable接口的close方法拋出異常類型Exception,所以,AutoCloseable接口的子類能夠覆蓋close方法的這種行爲,以拋出專門的異常,例如IOException,或者根本沒有異常。

把它們放在一塊兒

前面的部分描述瞭如何爲ListOfNumbers類中的writeList方法構造trycatchfinally代碼塊,如今,讓咱們來看看代碼並調查會發生什麼。

將全部組件放在一塊兒時,writeList方法以下所示。

public void writeList() {
    PrintWriter out = null;

    try {
        System.out.println("Entering" + " try statement");

        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           +  e.getMessage());
                                 
    } catch (IOException e) {
        System.err.println("Caught IOException: " +  e.getMessage());
                                 
    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

如前所述,此方法的try塊有三種不一樣的退出可能性,這是其中兩個。

  1. try語句中的代碼失敗並引起異常,這多是由new FileWriter語句引發的IOException或由for循環中的錯誤索引值引發的IndexOutOfBoundsException
  2. 一切都成功,try語句正常退出。

場景1:發生異常

建立FileWriter的語句可能因爲多種緣由而失敗,例如,若是程序沒法建立或寫入指示的文件,則FileWriter的構造函數將拋出IOException

FileWriter拋出IOException時,運行時系統當即中止執行try塊,正在執行的方法調用未完成,而後,運行時系統開始在方法調用堆棧的頂部搜索適當的異常處理程序。在此示例中,發生IOException時,FileWriter構造函數位於調用堆棧的頂部,可是,FileWriter構造函數沒有適當的異常處理程序,所以運行時系統在方法調用堆棧中檢查下一個方法 — writeList方法,writeList方法有兩個異常處理程序:一個用於IOException,另外一個用於IndexOutOfBoundsException

運行時系統按照它們在try語句以後出現的順序檢查writeList的處理程序,第一個異常處理程序的參數是IndexOutOfBoundsException,這與拋出的異常類型不匹配,所以運行時系統會檢查下一個異常處理程序 — IOException,這與拋出的異常類型相匹配,所以運行時系統結束搜索適當的異常處理程序,既然運行時已找到適當的處理程序,那麼執行該catch塊中的代碼。

異常處理程序執行後,運行時系統將控制權傳遞給finally塊,不管上面捕獲的異常如何,finally塊中的代碼都會執行,在這種狀況下,FileWriter從未打開過,不須要關閉,在finally塊完成執行後,程序繼續執行finally塊以後的第一個語句。

這是拋出IOException時出現的ListOfNumbers程序的完整輸出。

Entering try statement
Caught IOException: OutFile.txt
PrintWriter not open

如下清單中的後加*號的代碼顯示了在此方案中執行的語句:

public void writeList() {
   PrintWriter out = null; //******

    try {
        System.out.println("Entering try statement"); //*******
        out = new PrintWriter(new FileWriter("OutFile.txt")); //******
        for (int i = 0; i < SIZE; i++)
            out.println("Value at: " + i + " = " + list.get(i));
                               
    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           + e.getMessage());
                                 
    } catch (IOException e) {
        System.err.println("Caught IOException: " + e.getMessage()); //*****
    } finally {
        if (out != null) { //*****
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open"); /******
        }
    }
}

場景2:try塊正常退出

在這種狀況下,try塊範圍內的全部語句都成功執行,而且不會拋出異常,執行在try塊的末尾結束,運行時系統將控制傳遞給finally塊,由於一切都成功了,因此當控制到達finally塊時,PrintWriter會打開,這會關閉PrintWriter,一樣,在finally塊完成執行以後,程序繼續執行finally塊以後的第一個語句。

當沒有拋出異常時,這是ListOfNumbers程序的輸出。

Entering try statement
Closing PrintWriter

如下示例中的加*號的代碼顯示了在此方案中執行的語句。

public void writeList() {
    PrintWriter out = null; //*****
    try {
        System.out.println("Entering try statement"); //******
        out = new PrintWriter(new FileWriter("OutFile.txt")); //******
        for (int i = 0; i < SIZE; i++) //******
            out.println("Value at: " + i + " = " + list.get(i)); //******
                  
    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           + e.getMessage());

    } catch (IOException e) {
        System.err.println("Caught IOException: " + e.getMessage());
                                 
    } finally {
        if (out != null) { //******
            System.out.println("Closing PrintWriter"); //******
            out.close(); //*******
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

上一篇:捕獲或指定要求

下一篇:如何拋出異常

相關文章
相關標籤/搜索