![](/img/trans.png)
[英]Java try-with-resources statements were reproted as error at compile time
[英]Try-with-resources and return statements in java
我想知道在try-with-resources塊中放置return語句是否會阻止資源自動關閉。
try(Connection conn = ...) {
return conn.createStatement().execute("...");
}
如果我寫這樣的東西,連接會被關閉嗎? 在 Oracle 文檔中指出:
try-with-resources 語句確保每個資源在語句結束時關閉。
如果由於 return 語句而從未到達語句的末尾會發生什么?
根據Oracle 的教程,“無論 try 語句是正常完成還是突然完成,[資源] 都將關閉”。 它abruptly
定義為來自異常。
返回try
是一個突然完成的例子,如JLS 14.1所定義。
資源將自動關閉(即使使用return
語句),因為它實現了AutoCloseable
接口。 這是一個輸出“成功關閉”的示例:
public class Main {
public static void main(String[] args) {
try (Foobar foobar = new Foobar()) {
return;
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Foobar implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("closed successfully");
}
}
AutoCloseable
接口可以使代碼的執行順序乍一看令人困惑。 讓我們用一個例子來解釋一下:
public class Main {
// An expensive resource which requires opening / closing
private static class Resource implements AutoCloseable {
public Resource() {
System.out.println("open");
}
@Override public void close() throws Exception {
System.out.println("close");
}
}
// find me a number!
private static int findNumber() {
// open the resource
try(Resource resource = new Resource()) {
// do some business logic (usually involving the resource) and return answer
return 2 + 2;
} catch(Exception e) {
// resource encountered a problem
throw new IllegalStateException(e);
}
}
public static void main(String[] args) {
System.out.println(findNumber());
}
}
上面的代碼嘗試打開一些Resource
並使用該資源進行一些業務邏輯(在這種情況下只是一些算術)。 運行代碼將打印:
open
close
4
因此Resource
在退出 try-with-resource 塊之前關閉。 為了弄清楚到底發生了什么,讓我們重新組織findNumber()
方法。
private static int findNumber() {
// open the resource
int number;
try(Resource resource = new Resource()) {
// do some business logic and return answer
number = 2 + 2;
} catch(Exception e) {
// resource encountered a problem
throw new IllegalStateException(e);
}
return number;
}
從概念上講,這就是將return
放置在 try-with-resource 塊中時發生的事情。 return
操作移到 try-with-resource 塊之后,以允許AutoCloseable
對象在返回之前關閉。
因此我們可以得出結論,try-with-resource 塊中的return
操作只是語法糖,您不必擔心在AutoCloseable
關閉之前返回。
好的答案已經發布。 我只是采取了不同的方法,因為這感覺像是一個深入研究一些可能有一天會派上用場的細節的機會,它試圖通過閱讀一些字節碼來回答這個問題。
有幾個場景 - 看看
try
塊中的異常try
塊上退出期間關閉auto-closeable
時的異常closing
了auto-closeable
處理前面的異常期間資源close
執行。 第一種情況通常是在 java 中使用try-with
首要考慮。 我們可以嘗試通過查看字節碼來理解其他三種場景。 最后一個場景解決了您的問題。
分解下面main
方法的字節碼
import java.io.*;
class TryWith {
public static void main(String[] args) {
try(PrintStream ps = System.out) {
ps.println("Hey Hey");
return;
}
}
}
讓我們分小部分回顧一下(省略了一些細節)
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: astore_1
0:獲取靜態字段System.out
。
3:將字段存入LocalVariableTable
(lvt)中的slot 1。
查看 lvt 我們可以確認第一個插槽是java.io.PrintStream
並且它的名稱為ps
LocalVariableTable:
Start Length Slot Name Signature
4 35 1 ps Ljava/io/PrintStream;
0 39 0 args [Ljava/lang/String;
4: aload_1
5: ldc #3 // String Hey Hey
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
4:加載ps
( aload_1
)
5:從常量池加載常量( ldc
), hey hey
。
7:調用print line方法,這會從操作數棧中消耗ps
和hey hey
。
10: aload_1
11: ifnull 18
14: aload_1
15: invokevirtual #5 // Method java/io/PrintStream.close:()V
18: return
10 - 11:將ps
加載到操作數堆棧中。 檢查ps
是否為null
,如果為null
,則跳轉到18
並從函數return
。
14 - 18:加載ps
,調用close
並return
。
上述內容特別有趣,因為它表明如果Auto-Closeable
資源為null
並且不throw
異常, try-with
塊將起作用。 當然,即使它確實有效,也沒有實際意義——除非在try
塊中沒有訪問資源。 任何訪問都會導致 NPE。
以上也是正常流程,萬一出現異常怎么辦? 讓我們來看看異常表
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
這告訴我們,字節碼 4-10 之間的任何java.lang.Throwable
類型的異常都在目標 19 處處理。對於第 31 行的第 24-28 行也是如此。
19: astore_2
20: aload_1
21: ifnull 37
24: aload_1
25: invokevirtual #5 // Method java/io/PrintStream.close:()V
28: goto 37
19:將異常存儲到局部變量2
。
20 - 25:這與我們之前看到的模式相同,僅當ps
不為null
才會調用close
28:跳轉指令到37
37: aload_2
38: athrow
37:在位置2加載存儲在局部變量表中的對象,之前我們將異常存儲在這個位置。
38:拋出異常
但是怎么樣過程中發生異常的情況下close
,當close
已於帳戶前面的異常的執行。 讓我們回顧一下異常表
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
那是異常表的第二行,讓我們看一下target 31處對應的字節碼
31: astore_3
32: aload_2
33: aload_3
34: invokevirtual #7 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
37: aload_2
38: athrow
31:次要異常存儲在插槽 3 的局部變量中。
32:從插槽 3 重新加載原始異常。
33-34:將次要異常作為抑制異常添加到原始異常中。
37-38: 拋出新的異常,我們之前介紹過這些行。
重新審視我們在開頭列出的考慮因素
try
塊上退出期間關閉auto-closeable
時出現異常。try
塊abruptly
退出closing
的auto-closeable
處理前面的異常時的資源。try
塊abruptly
退出return
之前執行重新審視我們在字節碼中遇到的auto-closeable
資源為null
的有趣場景,我們可以用
import java.io.*;
class TryWithAnother {
public static void main(String[] args) {
try(PrintStream ps = null) {
System.out.println("Hey Hey");
return;
}
}
}
毫不奇怪,我們在控制台上得到輸出Hey Hey
,也不例外。
最后但非常重要的是要記住,這個字節碼是 JLS 的兼容實現。 這種方法對於確定您的實際執行需要什么非常方便,可能還有其他兼容的替代方案 - 在這種情況下我想不出任何。 但是,考慮到這一點,如果不指定我的javac
版本,此響應將不完整
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.