The Java libraries include many resources that must be closed manually(adv.手動地) by invoking a close method. Examples include InputStream,OutputStream, and java.sql.Connection. Closing resources is often overlooked(n.被忽視的;v.忽視) by clients, with predictably(adv.可以預見的是) dire performance consequences. While many of these resources use finalizers as a safety net, finalizers don’t work very well (Item 8).
Java 庫包含許多必須通過調用 close 方法手動關閉的資源。常見的有 InputStream、OutputStream 和 java.sql.Connection。關閉資源常常會被客戶端忽略,這會導致可怕的性能后果。雖然這些資源中的許多都使用終結器作為安全網,但終結器并不能很好地工作(Item-8)。
Historically, a try-finally statement was the best way to guarantee that a resource would be closed properly, even in the face of an exception or return:
從歷史上看,try-finally 語句是確保正確關閉資源的最佳方法,即使在出現異常或返回時也是如此:
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
This may not look bad, but it gets worse when you add a second resource:
這可能看起來不壞,但添加第二個資源時,情況會變得更糟:
// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
}
finally {
in.close();
}
}
It may be hard to believe, but even good programmers got this wrong most of the time. For starters, I got it wrong on page 88 of Java Puzzlers [Bloch05], and no one noticed for years. In fact, two-thirds of the uses of the close method in the Java libraries were wrong in 2007.
這可能難以置信。在大多數情況下,即使是優秀的程序員也會犯這種錯誤。首先,我在 Java Puzzlers [Bloch05]的 88 頁上做錯了,多年來沒有人注意到。事實上,2007 年發布的 Java 庫中三分之二的 close 方法使用都是錯誤的。
譯注:《Java Puzzlers》的中文譯本為《Java 解惑》
Even the correct code for closing resources with try-finally statements,as illustrated(v.闡明) in the previous two code examples, has a subtle deficiency. The code in both the try block and the finally block is capable of throwing exceptions. For example, in the firstLineOfFile method, the call to readLine could throw an exception due to a failure in the underlying physical device, and the call to close could then fail for the same reason. Under these circumstances, the second exception completely obliterates(vt.抹去) the first one. There is no record of the first exception in the exception stack trace, which can greatly complicate debugging in real systems—usually it’s the first exception that you want to see in order to diagnose the problem. While it is possible to write code to suppress the second exception in favor of the first, virtually no one did because it’s just too verbose.
使用 try-finally 語句關閉資源的正確代碼(如前兩個代碼示例所示)也有一個細微的缺陷。try 塊和 finally 塊中的代碼都能夠拋出異常。例如,在 firstLineOfFile 方法中,由于底層物理設備發生故障,對 readLine 的調用可能會拋出異常,而關閉的調用也可能出于同樣的原因而失敗。在這種情況下,第二個異常將完全覆蓋第一個異常。異常堆棧跟蹤中沒有第一個異常的記錄,這可能會使實際系統中的調試變得非常復雜(而這可能是希望出現的第一個異常,以便診斷問題)。雖然可以通過編寫代碼來抑制第二個異常而支持第一個異常,但實際上沒有人這樣做,因為它太過冗長。
All of these problems were solved in one fell swoop when Java 7 introduced the try-with-resources statement [JLS, 14.20.3]. To be usable with this construct, a resource must implement the AutoCloseable interface, which consists of a single void-returning close method. Many classes and interfaces in the Java libraries and in third-party libraries now implement or extend AutoCloseable. If you write a class that represents a resource that must be closed, your class should implement AutoCloseable too.
當 Java 7 引入 try-with-resources 語句 [JLS, 14.20.3]時,所有這些問題都一次性解決了。要使用這個結構,資源必須實現 AutoCloseable 接口,它由一個單獨的 void-return close 方法組成。Java 庫和第三方庫中的許多類和接口現在都實現或擴展了 AutoCloseable。如果你編寫的類存在必須關閉的資源,那么也應該實現 AutoCloseable。
Here’s how our first example looks using try-with-resources:
下面是使用 try-with-resources 的第一個示例:
// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
And here’s how our second example looks using try-with-resources:
下面是使用 try-with-resources 的第二個示例:
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
originals, but they provide far better diagnostics. Consider the firstLineOfFile method. If exceptions are thrown by both the readLine call and the (invisible) close, the latter exception is suppressed in favor of the former. In fact, multiple exceptions may be suppressed in order to preserve the exception that you actually want to see. These suppressed exceptions are not merely discarded; they are printed in the stack trace with a notation saying that they were suppressed. You can also access them programmatically with the getSuppressed method, which was added to Throwable in Java 7.
和使用 try-finally 的原版代碼相比,try-with-resources 為開發者提供了更好的診斷方式。考慮 firstLineOfFile 方法。如果異常是由 readLine 調用和不可見的 close 拋出的,則后一個異常將被抑制,以支持前一個異常。實際上,還可能會抑制多個異常,以保留實際希望看到的異常。這些被抑制的異常不會僅僅被拋棄;它們會被打印在堆棧跟蹤中,并標記它們被抑制。可以通過編程方式使用 getSuppressed 方法訪問到它們,該方法是在 Java 7 中添加到 Throwable 中的。
You can put catch clauses on try-with-resources statements, just as you can on regular try-finally statements. This allows you to handle exceptions without sullying your code with another layer of nesting. As a slightly contrived example, here’s a version our firstLineOfFile method that does not throw exceptions, but takes a default value to return if it can’t open the file or read from it:
可以在帶有資源的 try-with-resources 語句中放置 catch 子句,就像在常規的 try-finally 語句上一樣。這允許處理異常時不必用另一層嵌套來影響代碼。作為一個特指的示例,下面是我們的 firstLineOfFile 方法的一個版本,它不拋出異常,但如果無法打開文件或從中讀取文件,則返回一個默認值:
// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}
The lesson is clear: Always use try-with-resources in preference to tryfinally when working with resources that must be closed. The resulting code is shorter and clearer, and the exceptions that it generates are more useful. The try-with-resources statement makes it easy to write correct code using resources that must be closed, which was practically impossible using tryfinally.
教訓很清楚:在使用必須關閉的資源時,總是優先使用 try-with-resources,而不是 try-finally。前者的代碼更短、更清晰,生成的異常更有用。使用 try-with-resources 語句可以很容易地為必須關閉的資源編寫正確的代碼,而使用 try-finally 幾乎是不可能的。
Back to contents of the chapter(返回章節目錄)
- Previous Item(上一條目):Item 8: Avoid finalizers and cleaners(避免使用終結器和清除器)
- Next Item(下一條目):Chapter 3 Introduction(章節介紹)