1. 外部資源
當 Java 程序運行時,有時它會與 Java 機器外部的實體進行交互。例如,磁盤上的文件。這些實體通常稱為外部資源。內部資源是在 Java 機器內部創建的對象。
通常,交互遵循以下方案:
追踪資源
操作系統嚴格跟踪可用資源,並控制不同程序對它們的共享訪問。例如,如果一個程序更改了一個文件,那麼另一個程序就不能更改(或刪除)該文件。這個原則並不局限於文件,但它們提供了最容易理解的例子。
操作系統具有允許程序獲取和/或釋放資源的函數 (API)。如果資源繁忙,則只有獲取它的程序才能使用它。如果資源是免費的,那麼任何程序都可以獲取它。
想像一下,您的辦公室共用咖啡杯。如果有人拿了一個杯子,那麼其他人就不能再拿了。但是一旦杯子被使用、清洗並放回原位,任何人都可以再次使用它。公共汽車或地鐵上的座位情況是一樣的。如果有空位,那麼任何人都可以坐。如果一個座位被佔用,則它由佔用它的人控制。
獲取外部資源。
每次您的 Java 程序開始使用磁盤上的文件時,Java 機器都會請求操作系統對其進行獨占訪問。如果資源空閒,則 Java 機器會獲取它。
但是當你使用完這個文件之後,這個資源(文件)必須被釋放,也就是說你需要通知操作系統你不再需要它了。如果您不這樣做,那麼該資源將繼續由您的程序持有。
操作系統維護著每個正在運行的程序佔用的資源列表。如果你的程序超過了分配的資源限制,那麼操作系統將不再給你新的資源。
好消息是,如果您的程序終止,所有資源都會自動釋放(操作系統本身會這樣做)。
壞消息是,如果您正在編寫服務器應用程序(並且很多服務器應用程序是用 Java 編寫的),您的服務器需要能夠連續運行數天、數周和數月而不停止。如果您每天打開 100 個文件並且不關閉它們,那麼幾週後您的應用程序將達到其資源限制並崩潰。這遠遠低於數月的穩定工作。
2.close()
方法
使用外部資源的類有一種釋放它們的特殊方法:close()
.
下面我們提供了一個程序示例,該程序將某些內容寫入文件,然後在完成後關閉文件,即釋放操作系統的資源。它看起來像這樣:
代碼 | 筆記 |
---|---|
|
文件的路徑。 獲取文件對象:獲取資源。 寫入文件 關閉文件 - 釋放資源 |
使用文件(或其他外部資源)後,您必須在close()
鏈接到外部資源的對像上調用該方法。
例外情況
一切看起來都很簡單。但是程序運行時可能會出現異常,外部資源不會被釋放。那是非常糟糕的。
為了確保該方法始終被close()
調用,我們需要將我們的代碼包裝在一個try
--塊中,並將該方法添加到該塊中。它看起來像這樣:catch
finally
close()
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 編寫代碼 |
---|---|
|
|
使用 -with-resources 的代碼try
更短且更易於閱讀。我們擁有的代碼越少,出現拼寫錯誤或其他錯誤的可能性就越小。
順便說一下,我們可以在 -with-resources 語句中添加catch
和finally
塊try
。或者,如果不需要,您可以不添加它們。
4.同時有幾個變量
順便說一句,您可能經常會遇到需要同時打開多個文件的情況。假設您正在復制一個文件,那麼您需要兩個對象:要從中復制數據的文件和要將數據複製到的文件。
在這種情況下,try
-with-resources 語句允許您在其中創建一個但多個對象。創建對象的代碼必須用分號分隔。以下是此語句的一般外觀:
try (ClassName name = new ClassName(); ClassName2 name2 = new ClassName2())
{
Code that works with the name and name2 variables
}
複製文件示例:
長碼 | 短代碼 |
---|---|
|
|
那麼,我們能在這裡說什麼呢?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 有很多實現此接口的類,因此很可能一切都會按預期工作。
GO TO FULL VERSION