1. 外部資源

當 Java 程序運行時,有時它會與 Java 機器外部的實體進行交互。例如,磁盤上的文件。這些實體通常稱為外部資源。內部資源是在 Java 機器內部創建的對象。

通常,交互遵循以下方案:

Try-with-resources 語句

追踪資源

操作系統嚴格跟踪可用資源,並控制不同程序對它們的共享訪問。例如,如果一個程序更改了一個文件,那麼另一個程序就不能更改(或刪除)該文件。這個原則並不局限於文件,但它們提供了最容易理解的例子。

操作系統具有允許程序獲取和/或釋放資源的函數 (API)。如果資源繁忙,則只有獲取它的程序才能使用它。如果資源是免費的,那麼任何程序都可以獲取它。

想像一下,您的辦公室共用咖啡杯。如果有人拿了一個杯子,那麼其他人就不能再拿了。但是一旦杯子被使用、清洗並放回原位,任何人都可以再次使用它。公共汽車或地鐵上的座位情況是一樣的。如果有空位,那麼任何人都可以坐。如果一個座位被佔用,則它由佔用它的人控制。

獲取外部資源

每次您的 Java 程序開始使用磁盤上的文件時,Java 機器都會請求操作系統對其進行獨占訪問。如果資源空閒,則 Java 機器會獲取它。

但是當你使用完這個文件之後,這個資源(文件)必須被釋放,也就是說你需要通知操作系統你不再需要它了。如果您不這樣做,那麼該資源將繼續由您的程序持有。

操作系統維護著每個正在運行的程序佔用的資源列表。如果你的程序超過了分配的資源限制,那麼操作系統將不再給你新的資源。

好消息是,如果您的程序終止,所有資源都會自動釋放(操作系統本身會這樣做)。

壞消息是,如果您正在編寫服務器應用程序(並且很多服務器應用程序是用 Java 編寫的),您的服務器需要能夠連續運行數天、數周和數月而不停止。如果您每天打開 100 個文件並且不關閉它們,那麼幾週後您的應用程序將達到其資源限制並崩潰。這遠遠低於數月的穩定工作。


2.close()方法

使用外部資源的類有一種釋放它們的特殊方法:close().

下面我們提供了一個程序示例,該程序將某些內容寫入文件,然後在完成後關閉文件,即釋放操作系統的資源。它看起來像這樣:

代碼 筆記
String path = "c:\\projects\\log.txt";
FileOutputStream output = new FileOutputStream(path);
output.write(1);
output.close();
文件的路徑。
獲取文件對象:獲取資源。
寫入文件
關閉文件 - 釋放資源

使用文件(或其他外部資源)後,您必須在close()鏈接到外部資源的對像上調用該方法。

例外情況

一切看起來都很簡單。但是程序運行時可能會出現異常,外部資源不會被釋放。那是非常糟糕的。

為了確保該方法始終被close()調用,我們需要將我們的代碼包裝在一個try--塊中,並將該方法添加到該塊中。它看起來像這樣:catchfinallyclose()finally

try
{
   FileOutputStream output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

這段代碼不會編譯,因為output變量是在try {}塊內聲明的,因此在finally塊中是不可見的。

讓我們修復它:

FileOutputStream output = new FileOutputStream(path);

try
{
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

沒關係,但是如果我們在創建對象時發生錯誤,它就不會起作用FileOutputStream,而且很容易發生這種情況。

讓我們修復它:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
   output.close();
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   output.close();
}

還有一些批評。首先,如果創建對象時發生錯誤FileOutputStream,則output變量將為空。必須在塊中考慮這種可能性finally

其次,該close()方法總是在finally塊中調用,這意味著它在try塊中是沒有必要的。最終代碼將如下所示:

FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
catch (IOException e)
{
   e.printStackTrace();
}
finally
{
   if (output != null)
      output.close();
}

即使我們不考慮catch塊,可以省略,那麼我們的3行代碼就變成了10行。但是我們基本上只是打開文件寫了1。有點麻煩,你不覺得嗎?


3.-try資源

在這裡,Java 的創造者決定給我們撒上一些語法糖。從第 7 版開始,Java 有一個新的try-with-resources 語句。

它的創建正是為了解決強制調用方法的問題close()。一般情況看起來很簡單:

try (ClassName name = new ClassName())
{
     Code that works with the name variable
}

這是try 聲明的另一種變體。需要在try關鍵字後面加上括號,然後在括號內創建帶有外部資源的對象。對於括號中的每個對象,編譯器都會添加一個finally部分和對方法的調用close()

下面是兩個等價的例子:

長碼 使用 try-with-resources 編寫代碼
FileOutputStream output = null;

try
{
   output = new FileOutputStream(path);
   output.write(1);
}
finally
{
   if (output != null)
   output.close();
}
try(FileOutputStream output = new FileOutputStream(path))
{
   output.write(1);
}

使用 -with-resources 的代碼try更短且更易於閱讀。我們擁有的代碼越少,出現拼寫錯誤或其他錯誤的可能性就越小。

順便說一下,我們可以在 -with-resources 語句中添加catchfinallytry。或者,如果不需要,您可以不添加它們。



4.同時有幾個變量

順便說一句,您可能經常會遇到需要同時打開多個文件的情況。假設您正在復制一個文件,那麼您需要兩個對象:要從中復制數據的文件和要將數據複製到的文件。

在這種情況下,try-with-resources 語句允許您在其中創建一個但多個對象。創建對象的代碼必須用分號分隔。以下是此語句的一般外觀:

try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
   Code that works with the name and name2 variables
}

複製文件示例:

長碼 短代碼
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

FileInputStream input = null;
FileOutputStream output = null;

try
{
   input = new FileInputStream(src);
   output = new FileOutputStream(dest);

   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}
finally
{
   if (input != null)
      input.close();
   if (output != null)
      output.close();
}
String src = "c:\\projects\\log.txt";
String dest = "c:\\projects\\copy.txt";

try(FileInputStream input = new FileInputStream(src);

FileOutputStream output = new FileOutputStream(dest))
{
   byte[] buffer = input.readAllBytes();
   output.write(buffer);
}

那麼,我們能在這裡說什麼呢?try-with-resources 是一件很棒的事情!


5.AutoCloseable界面

但這還不是全部。細心的讀者會立即開始尋找限制該陳述應用方式的陷阱。

try但是,如果類沒有方法,-with-resources 語句如何工作close()?好吧,假設什麼都不會被調用。沒有方法,沒有問題。

try但是如果類有多個close()方法,-with-resources 語句如何工作呢?他們需要將參數傳遞給他們嗎?而且這個類沒有沒有close()參數的方法?

我希望你真的問過自己這些問題,也許還有其他問題。

為了避免此類問題,Java 的創建者提出了一個名為 的特殊接口AutoCloseable,它只有一個方法 — close(),沒有參數。

他們還添加了限制,即只有實現類的對象AutoCloseable才能在try-with-resources 語句中聲明為資源。因此,此類對象將始終具有close()不帶參數的方法。

順便說一下,您認為try-with-resources 語句可以將一個對象聲明為資源,該對象的類有自己的close()不帶參數的方法但沒有實現AutoCloseable

壞消息:正確答案是否定的——類必須實現接口AutoCloseable

好消息: Java 有很多實現此接口的類,因此很可能一切都會按預期工作。