資料來源:Medium 本指南介紹了 try-with-resource 相對於 try-catch-finally 的優勢。您還將了解在什麼條件下引發抑制異常以及如何將 try-with-resources 與多個資源一起使用。 try with resources茶歇#203。 如何使用 try-with-resource 語句處理異常 - 1構造,也稱為try-with-resources,是 Java 中的異常處理機制,可以在完成使用 Java InputStream 或 JDBC Connection 等資源時自動關閉這些資源。2011年,Oracle在Java語言語法中新增了try with resources,以確保網路套接字、資料庫連線、檔案和資料夾連結等物件在使用後能夠正常關閉。如果開發人員在開啟這些資源的句柄後未能關閉這些資源,可能會導致記憶體洩漏、觸發可預防的垃圾收集例程以及伺服器上的 CPU 開銷。

Java 7 之前

在Java中,如果使用輸入/輸出流等資源,則總是需要在使用後關閉它們。由於它們也可以拋出異常,因此它們必須包含在try-catch區塊中。結束必鬚髮生在finally區塊中。至少在 Java 7 之前。但它有幾個缺點:
  • 在關閉資源之前,您需要檢查資源是否為
  • 閉包本身可能會引發異常,因此您必須在finally區塊中再新增一個try-catch
  • 程式設計師往往會忘記關閉他們的資源。

如何使用try-with-resource語句?

這個運算符最初是在 Java 7 中引入的,其想法是開發人員不再需要擔心管理他們在try-catch-finally區塊中使用的資源。這是透過消除對finally區塊的需要來實現的,實際上,開發人員僅使用finally區塊來關閉資源。在 Java 中,try-with-resources語句是宣告一個或多個資源的try語句。資源是程式終止後必須關閉的物件。當程式碼執行離開try-with-resources區塊時,在try-with-resources區塊中開啟的任何資源都會自動關閉,無論在try-with-resources 區塊內或嘗試關閉資源時是否引發任何異常。若要使用 Java try-with-resources語言功能,請遵循下列規則:
  • try-with-resources語句控制的所有物件都必須實作AutoCloseable介面。
  • 可以在try-with-resources區塊中建立多個AutoCloseable物件。
  • 在try-with-resources語句中宣告的物件在try區塊中執行,但不在catchfinally區塊中執行。
  • 無論運行時是否拋出異常,都會呼叫在try-with-resources區塊中宣告的物件的close()方法。
  • 如果在close()方法中拋出異常,則它可能被歸類為抑制異常。
Catchfinally區塊仍然可以在try-with-resource區塊中使用,並且它們的工作方式與在常規try區塊中相同。如果使用try-with-resource ,則AutoCloseable物件所引用的資源將始終關閉。這消除了通常由資源分配不當引起的潛在記憶體洩漏。

句法

try(declare resources here) {
    // использовать ресурсы
}
catch(FileNotFoundException e) {
    // обработка исключений
}

try-with-resource 的實際使用

若要自動關閉,必須在try內宣告並初始化資源:
try (PrintWriter writer = new PrintWriter(new File("test.txt"))) {
    writer.println("Hello World");
}

用 try-with-resources 取代 try-catch-finally

使用try-with-resources功能的簡單而明顯的方法是取代傳統且冗長的try-catch-finally區塊。讓我們來比較一下下面的程式碼範例。第一個範例是典型的try-catch-finally區塊:
Scanner scanner = null;
try {
    scanner = new Scanner(new File("test.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}
這是一個使用try-with-resources 的新的超級簡潔解決方案:
try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

try 和 try-with-resource 之間的區別

當涉及到異常時,try-catch-finally區塊和try-with-resources區塊之間存在差異。tryfinally區塊中都會引發異常,但此方法只會傳回finally區塊中引發的異常。對於try-with-resources,如果try區塊和try-with-resources語句中發生異常,則該方法將傳回try區塊中拋出的異常。try-with-resources區塊拋出的異常是被抑制的,也就是說,try-with-resources區塊拋出的異常是被抑制的。

為什麼應該使用 try-with-resource 語句?

try-with-resources語句確保每個資源在語句結束時關閉。如果我們不關閉資源,這可能會導致資源洩漏,並且程式可能會耗盡可用的資源。當您使用try-catch-finally區塊時會發生這種情況。在Java SE 7之前,您可以使用finally區塊來確保無論try語句是正常退出還是突然退出,資源都會被關閉。以下範例使用finally區塊而非try-with-resources語句:
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {

    FileReader fr = new FileReader(path);
    BufferedReader br = new BufferedReader(fr);
    try {
        return br.readLine();
    } finally {
        br.close();
        fr.close();
    }
}
此範例中可能存在資源洩漏。程式必須做的不僅僅是依靠垃圾收集器在使用完資源後釋放資源的記憶體。程式還必須將資源傳回給作業系統,通常是透過呼叫資源的 close 方法。但是,如果程式在垃圾收集器傳回資源之前不執行此操作,則釋放資源所需的資訊將會遺失。作業系統仍認為正在使用的資源已洩漏。在上面的範例中,如果readLine方法拋出異常,並且finally區塊中的br.close()語句拋出異常,則FileReader已洩漏。這就是為什麼您最好使用try-with-resources語句而不是finally區塊來關閉程式的資源。

再舉一個例子

以下範例從檔案中讀取第一行。FileReaderBufferedReader實例用於讀取資料。這些是程序退出後需要關閉的資源。
import java.io.*;
class Main {
  public static void main(String[] args) {
    String line;
    try(BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
      while ((line = br.readLine()) != null) {
        System.out.println("Line =>"+line);
      }
    } catch (IOException e) {
      System.out.println("IOException in try block =>" + e.getMessage());
    }
  }
}
如您所見,try-with-resources語句中聲明的資源是BufferedReader。此資源的聲明語句出現在緊接在try關鍵字後面的括號中。Java SE 7 及更高版本中的BufferedReader類別實作java.lang.AutoCloseable介面。由於BufferedReader實例是在try-with-resource語句中聲明的,因此無論try語句正常退出還是突然退出(如果BufferedReader.readLine()方法拋出IOException),它們都會被關閉。

抑制異常

如果try區塊拋出異常,且try-with-resources區塊中出現一個或多個異常,則try-with-resources區塊拋出的異常將被抑制。換句話說,我們可以說try-with-resources拋出的異常是抑制異常。您可以使用Throwable類別的getSuppress()方法來捕捉這些異常。在前面顯示的範例中,在下列條件下,try-with-resources語句引發異常:
  • 找不到檔案test.txt 。
  • 關閉BufferedReader物件。
try區塊也可能引發異常,因為讀取檔案隨時可能因多種原因而失敗。如果try區塊和try-with-resources語句都拋出異常,則在第一種情況下會拋出異常,在第二種情況下會抑制異常。

獲取被抑制的異常

在 Java 7 及更高版本中,可以透過從try塊引發的異常呼叫Throwable.getSuppressed()方法來取得受抑制的異常。 getSuppress()傳回一個數組,其中包含由try-with-resources語句抑制的所有異常。如果沒有抑制異常或停用抑制,則傳回空數組。以下是在catch區塊中接收抑制異常的範例:
catch(IOException e) {
  System.out.println("Thrown exception=>" + e.getMessage());
  Throwable[] suppressedExceptions = e.getSuppressed();
  for (int i=0; i" + suppressedExceptions[i]);
  }
}

使用資源嘗試的好處

  • 可讀且易於編寫程式碼。
  • 自動資源管理。
  • 程式碼行數已減少。
  • 當在try-with-resources中開啟多個資源時,它們會以相反的順序關閉,以避免依賴問題。
當然,關閉資源不再需要finally區塊。以前,在Java 7之前,我們必須使用finally區塊來確保資源關閉以避免資源外洩。這是一個與第一個範例類似的程式。在這個程式中,我們使用了finally區塊來關閉資源。
import java.io.*;
class Main {
  public static void main(String[] args) {
    BufferedReader br = null;
    String line;
    try {
      System.out.println("Entering try block");
      br = new BufferedReader(new FileReader("test.txt"));
      while ((line = br.readLine()) != null) {
        System.out.println("Line =>"+line);
      }
    } catch (IOException e) {
      System.out.println("IOException in try block =>" + e.getMessage());
    } finally {
      System.out.println("Entering finally block");
      try {
        if (br != null) {
          br.close();
        }
      } catch (IOException e) {
        System.out.println("IOException in finally block =>"+e.getMessage());
      }
    }
  }
}
結論:
輸入 try 區塊 Line => test.txt 檔案中的行 輸入 finally 區塊
從上面的範例可以看出,使用finally區塊來清理資源會增加程式碼的複雜度。注意到finally區塊中的try...catch 區塊了嗎?這是因為在finally區塊內關閉BufferedReader實例時也可能發生IOException ,因此它也會被捕獲並處理。try-with-resources語句執行自動資源管理。我們不需要明確關閉資源,因為 JVM 會自動關閉它們。這使得程式碼更具可讀性並且更容易編寫。

嘗試使用多種資源

我們可以在try-with-resources區塊中宣告多個資源,只需用分號分隔它們即可:
try (Scanner scanner = new Scanner(new File("testRead.txt"));
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    while (scanner.hasNext()) {
	writer.print(scanner.nextLine());
    }
}

Java 9 - 有效的最終變數

在 Java 9 之前,我們只能在try-with-resources區塊中使用新變數:
try (Scanner scanner = new Scanner(new File("testRead.txt"));
    PrintWriter writer = new PrintWriter(new File("testWrite.txt"))) {
    // omitted
}
請注意,在聲明多個資源時,這非常冗長。從 Java 9(JEP 213 更新)開始,我們可以在try-with-resources區塊中使用Final甚至有效的 Final變數:
final Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))
try (scanner;writer) {
    // omitted
}
簡而言之,如果一個變數在第一次賦值後沒有被修改,那麼它實際上就是final,即使它沒有明確標記為final。如上所示,掃描器變數被明確聲明為最終變量,因此我們可以將其與try-with-resources區塊一起使用。儘管 writer 變數不是明確的final,但它在第一次賦值後不會改變。所以我們也可以使用writer變數。我希望今天您能夠更好地理解如何使用try-with-resource語句處理異常。快樂學習!