【问题标题】: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 会弄清楚如何做到这一点,还是我应该保持不变?

【问题讨论】:

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

标签: java android


【解决方案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 来解决一个问题。另一点是有效的,但我尽量保持原始问题的代码不变,以便能够看到哪些变化和哪些保持不变(但后来提到实际示例不是一个好例子)。跨度>
猜你喜欢
  • 2014-01-01
  • 1970-01-01
  • 2018-09-09
  • 2014-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-27
  • 2017-10-15
相关资源
最近更新 更多