簡體   English   中英

Java 中的 Try-with-resources 和 return 語句

[英]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時的異常
  • 異常,當closingauto-closeable處理前面的異常期間資源
  • 在 try 塊中 return,在 return 之前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:加載psaload_1
5:從常量池加載常量( ldc ), hey hey
7:調用print line方法,這會從操作數棧中消耗pshey 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 ,調用closereturn

上述內容特別有趣,因為它表明如果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時出現異常。
    ** 引發異常並且tryabruptly退出
  • 當異常closingauto-closeable處理前面的異常時的資源。
    ** 一個被抑制的異常被添加到原始異常並拋出原始異常。 tryabruptly退出
  • 在 try 塊中 return,在 return 之前 close 執行。
    ** close 在 try 塊中的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM