在 Java 中对资源使用 try 是不是安全 - 它是不是检查可关闭对象是不是不为空,是不是在尝试关闭它时捕获异常

Posted

技术标签:

【中文标题】在 Java 中对资源使用 try 是不是安全 - 它是不是检查可关闭对象是不是不为空,是不是在尝试关闭它时捕获异常【英文标题】:Is it safe to use try with resources in Java - does it check if the closeable is not null and does it catch exceptions while trying to close it在 Java 中对资源使用 try 是否安全 - 它是否检查可关闭对象是否不为空,是否在尝试关闭它时捕获异常 【发布时间】:2017-10-30 16:02:10 【问题描述】:

android 中使用 Java 的 try with resources 是否安全 - 它是否检查可关闭对象是否不为空,是否在尝试关闭它时捕获 close 抛出的异常?

如果我转换这个:

    try 
        inChannel.transferTo(0, inChannel.size(), outChannel);
     finally 
        if (inChannel != null) 
            inChannel.close();
        
        if (outChannel != null) 
            outChannel.close();
        
    

    try (FileChannel inChannel = new FileInputStream(src).getChannel(); 
         FileChannel outChannel = new FileOutputStream(dst).getChannel()) 
        inChannel.transferTo(0, inChannel.size(), outChannel);
    

它会在尝试调用close 之前检查inChanneloutChannel 是否不为空吗?

另外,在这里使用 try 与资源是否安全:

    try 
        cursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]MediaStore.Images.Media.DATA,
                MediaStore.Images.Media._ID + " =? ",
                new String[]"" + imageIdInMediaStore,
                null);
        if (cursor != null && cursor.getCount() > 0) 
            cursor.moveToFirst();
            return cursor.getString(0);
         else 
            return "";
        
     catch (Exception e) 
        return "";
     finally 
        if (cursor != null && !cursor.isClosed()) 
            cursor.close();
            cursor = null;
        
    

finally 块对 !cursor.isClosed() 进行了重要检查 - try with resources 会弄清楚如何做到这一点,还是我应该保持不变?

【问题讨论】:

***.com/questions/23611940/… 为什么说cursor.isClosed()“重要的检查”?只需致电cursor.close()close() 方法要求是幂等的,所以 isClosed() 调用是多余的。在已经关闭的 cursor 上调用 close() 没有任何作用。正如javadoc 所说:如果流已关闭,则调用此方法无效。 为什么它们会为空?他们怎么可能是空的? 【参考方案1】:

找出自己最简单的方法是编写一个测试类,简单地告诉你它:

import junit.framework.TestCase;

public class __Test_AutoClosable extends TestCase 

    public void testWithNull() throws Exception 
        try (TestClosable tc = getNull()) 
            assertNull("check existance of closable", tc);
        
    

    public void testNonNullWithoutException() throws Exception 
        try (TestClosable tc = getNotNull(false)) 
            assertNotNull("check existance of closable", tc);
        
    

    public void testNonNullWithException() throws Exception 
        try (TestClosable tc = getNotNull(true)) 
            assertNotNull("check existance of closable", tc);
        
        catch(Exception e) 
            assertEquals("check message", "Dummy Exception", e.getMessage());
        
    

    TestClosable getNull() 
        return null;
    

    TestClosable getNotNull(boolean throwException) 
        return new TestClosable(throwException);
    

    static class TestClosable implements AutoCloseable 
        private boolean throwException;
        TestClosable(boolean throwException) 
            this.throwException = throwException;
        
        @Override
        public void close() throws Exception 
            if (throwException) 
                throw new Exception("Dummy Exception");
            
        
    

这个类运行没有错误,所以你的问题的答案是:

是的,null 作为响应是可能的,它在关闭阶段检查 close 上抛出的异常不会被捕获,但必须自己捕获和处理

如果您稍微考虑一下,那是完全有道理的。不支持 null 会使整个构造变得毫无用处,简单地忽略关闭,即使它们是隐式完成的,也是一件坏事。

编辑:上面的测试用例是“真实”测试的结果,例如被添加到您的源代码库中,您实际上应该检查是否已调用 autoclosable 中的关闭方法。

关于您的第二个问题:如果在某些情况下您不希望关闭发生,则 1:1 更改为 try-with-resources 不起作用,但您可以执行以下操作:

public class CursorClosable implements AutoClosable 
    private Cursor cursor;

    public CursorClosable(Cursor cursor) 
        this.cursor = cursor;
    

    public Cursor getCursor() 
        return cursor;
    

    @Override
    public void close() throws Exception 
        if (cursor != null && !cursor.isClosed()) 
            cursor.close();
        
    

您的新代码将如下所示:

try (Cursor cursorac = new CursorClosable(context.getContentResolver().query(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            new String[]MediaStore.Images.Media.DATA,
            MediaStore.Images.Media._ID + " =? ",
            new String[]"" + imageIdInMediaStore,
            null)) 
    Cursor cursor = cursorac.getCursor();
    if (cursor != null && cursor.getCount() > 0) 
        cursor.moveToFirst();
        return cursor.getString(0);
     else 
        return "";
    
 catch (Exception e) 
    return "";

我不确定检查isClosed 是否真的有必要,所以对于这个特定示例,您可能不需要这种“hack”,但对于您不想关闭的其他示例,它仍然有效资源。

【讨论】:

但是,如果 try 块抛出异常,然后 close 调用也抛出异常,则 close 异常不会覆盖/替换第一个异常,而是附加将第一个异常作为 suppressed 异常。见The Java™ Tutorials - The try-with-resources Statement - Suppressed Exceptions。 谢谢。因此,如果try with resources 如果close 方法可能引发异常,或者如果有try with resources 不知道的其他检查(例如if !cursor.isClosed() @K.R.只要资源对象实现CloseableAutoCloseable,您当然可以受益。像isClosed() 这样的调用是不必要的,因为try-with-resources 块应该是唯一一个关闭对象的方法。此外,close 方法应该是幂等的,这意味着应该忽略重复调用。 @Lothar 在更新的答案中,CursorClosablecursor 变量不能为空。块内的所有代码都必须将cursor 更改为cursor.getCursor()。不需要添加CursorClosable,因为isClosed() 调用是多余的,因为close() 必须是idempotent。 @idempotent 我将 try-with-resource-block 中的结果名称更改为其他名称,并在实际块中调用 getCursor 来解决一个问题。另一点是有效的,但我尽量保持原始问题的代码不变,以便能够看到哪些变化和哪些保持不变(但后来提到实际示例不是一个好例子)。跨度>

以上是关于在 Java 中对资源使用 try 是不是安全 - 它是不是检查可关闭对象是不是不为空,是不是在尝试关闭它时捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

在java中对每个方法使用一个大的try-catch是一种已知的好习惯吗? [关闭]

为要由 java 的 try with resources 语句管理的资源声明自变量

Java 代码字节:足智多谋的 Try-With-Resources

如果我们使用 try-with-resource 是不是需要关闭资源

java7与java9中的try-finally关闭资源

java7与java9中的try-finally关闭资源