为什么try-with-resources语句中的catch块是可选的?

31

我读到try-with-resources中的catch块是可选的。我试图在try-with-resources块中创建一个Connection对象,并没有后续的catch块,结果从eclipse编译器得到了错误提示:"Unhandled exception type SQLException thrown by automatic close() invocation."。

由于每个可以在try-with-resources中使用的资源都实现了AutoCloseable接口,并且在调用close()方法时可能会抛出异常,因此我不明白为什么在不捕获close()方法引发的异常的情况下可以省略catch子句。既然它不允许我跳过捕获来自close()的异常,那么为什么catch子句是可选的?

是否有一些特殊要求,即AutoCloseable的具体实现不能直接声明其close()方法抛出的任何异常?例如,用不抛出任何异常的close()重写AutoCloseableclose() throws Exception

或者这可能只是一个eclipse问题吗?

编辑:这里是最简单的代码片段,仍然会触发该问题:

try (Connection con = dataSource.getConnection()) {
  /*...*/

}

请问这是否与使用 JNDI 数据源有关?

提前感谢。


尝试在没有Eclipse的情况下在命令行上编译。 - Romski
2
您可以提供一个AutoClosable实现,它不会抛出异常,这样您就不需要捕获任何东西,或者您可以在方法签名中添加一个throws子句,这样您就不需要一个catch子句。 - Edwin Dalorzo
谢谢你的回答和关于包含方法中throws的注释,Edwin。我忘记了这样做可以使try语句不必捕获异常。 - Mer
5个回答

27

如果close()无法抛出已检查的异常,则可以选择不使用。但是,如果close()可以抛出已检查的异常,则需要按照正常方式处理已检查的异常,要么使用catch块,要么通过从包含try-with-resources块的方法中抛出。

更多详情请参阅JLS 14.2.3

  

14.20.3.2. 扩展的try-with-resources

     

带有至少一个catch子句和/或finally子句的try-with-resources语句称为扩展的try-with-resources语句。

     

扩展的try-with-resources语句的含义:

try ResourceSpecification
    Block
[Catches]
[Finally]
以下是将其转换为基本的try-with-resources语句嵌套在try-catch或try-finally或try-catch-finally语句中的翻译:
try {
    try ResourceSpecification
       Block
}
[Catches]
[Finally]
將資源規範放置於 try 陳述式「內部」的翻譯作用是將資源規範放在 try 陳述式「內部」。這允許擴展的 try-with-resources 陳述式的 catch 子句捕獲由任何資源的自動初始化或關閉引起的異常。
此外,所有資源都將在 finally 塊執行時關閉(或嘗試關閉),符合 finally 關鍵字的目的。
關於是否與使用 JNDI DataSource 相關的想法?
是相關的。
在您提供的 try-with-resources 區塊示例中,有必要捕獲異常並進行處理,或者從方法中拋出異常,因為 SQLException 是一個已檢查的異常。

感谢您提供详细的答案。我很惊讶,因为我认为try-with-resources是用来捕获在“内部”的try子句中指定的异常的。现在我明白了,AutoCloseable的实现必须覆盖close()方法,以便不抛出可选的异常,因为try-with-resources不会隐式捕获它。 - Mer
1
@Mer 语言规范通常是这类问题的好资源。 - jdphenix

3
你可以直接抛出异常(或在另一个try-catch块中捕获异常):
private static void test() throws IOException {
    try(InputStream is = new FileInputStream("test.txt")) {
        while(is.read() > -1) {
        }
    } finally {
        // Will get executed, even if exception occurs
        System.out.println("Finished");
    }
}

1
您可以通过声明AutoClosable的close()方法不带任何异常或带有RuntimeException来创建一个不需要显式catch块的AutoClosable。如果没有任何异常,那么很明显不需要catch块。此外,编译器不会静态检查是否需要捕获RuntimeException(与已检查的异常相比)。
示例:
public class AutoClosableDemo
{

    public static void main( final String[] args )
    {
        try (MyAutoCloseable1 mac1 = new MyAutoCloseable1())
        {
            System.out.println( "try-with-resource MyAutoCloseable1" );
        }
        try (MyAutoCloseable2 mac2 = new MyAutoCloseable2())
        {
            System.out.println( "try-with-resource MyAutoCloseable2" );
        }
        // The following is not allowed, because
        // "Unhandled exception type Exception thrown by automatic close() invocation on mac3"
        // try (MyAutoCloseable3 mac3 = new MyAutoCloseable3())
        // {
        // System.out.println( "try-with-resource MyAutoCloseable13" );
        // }
        System.out.println( "done" );
    }

    public static class MyAutoCloseable1 implements AutoCloseable
    {
        @Override
        public void close()
        {
            System.out.println( "MyAutoCloseable1.close()" );
        }
    }

    public static class MyAutoCloseable2 implements AutoCloseable
    {
        @Override
        public void close() throws RuntimeException
        {
            System.out.println( "MyAutoCloseable2.close()" );
        }
    }

    public static class MyAutoCloseable3 implements AutoCloseable
    {
        @Override
        public void close() throws Exception
        {
            System.out.println( "MyAutoCloseable3.close()" );
        }
    }
}

0

您可以查看JLS,但实际上有一个相对简单的理由,解释了为什么这是语言唯一正确的行为方式。

受检异常的主要规则是:方法声明的任何受检异常都必须被处理,可以通过捕获或让调用方法抛出来处理。

try-with-resources总是(隐式)调用close方法。

因此,如果您使用的AutoClosable的特定关闭方法(由try中声明的类型确定)声明要引发受检异常,例如SQLException,则确实需要在某个地方处理此受检异常,否则将有可能违反该规则!

如果close方法没有声明它会引发受检异常,那么就不会违反规则,您也不需要为隐式调用close方法处理受检异常。如果您尝试捕获从未声明为引发异常的受检异常,实际上会导致编译失败。


-1

并不是每个Java类(!)都会抛出异常。有时你只想使用try-with-resources来使用自动关闭功能,仅此而已。

BufferedReader br = new BufferedReader(new FileReader(path));
try {
    return br.readLine();
} finally {
    if (br != null) br.close();
}

这个catch是可选的,因为readLine()不会抛出(已检查的)异常。

是的,close()可能会抛出异常,但是try-with-resources也可以处理它。

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
} 

因此,这个带资源的 try 不需要 catch。


我展示了try-with-resources替换的代码。它也没有catch,这就是为什么try-with-resources必须使catch变成可选的原因。 - markspace
你创建资源的地方(在这种情况下为缓冲读取器)可能会抛出IOException。如果你使用了try-with-resources,你需要有一个catch语句来处理异常。 - Edwin Dalorzo
3
请提供需要翻译的原文所在语言。 - Edwin Dalorzo
你的例子存在问题,如果我将其复制并粘贴到我的“main”方法中,它将产生与OP引起的完全相同的编译器错误。因此,你的解释只是陈述了OP已经知道的内容,而没有清楚地说明如何可能。无论如何,我不再争论。 - Edwin Dalorzo
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接