Java中的Closeable是否应该被用作.NET中IDisposable的等价物?

31
更新:正如@PaulGroke在下面指出的,Java 7 中的情况发生了变化:现在有 AutoCloseable 接口。它并不仅限于流并且被新的 try-with-resources 结构支持。 AutoCloseable 是直接相当于 .NET 的 IDisposable 接口的 Java 等效物。
Closeable 接口是在 Java 1.5 中引入的,它与流紧密绑定,并且甚至有一个用于 IOException 的异常说明符。这表明它应该仅用于流或其他 IO 相关活动,而不是通用的清理逻辑。
当然,close() 方法的描述在流 / IO 上下文之外完全没有意义:

void close() throws IOException

关闭此流并释放与之相关联的任何系统资源。

因此,我是否应该声明自己的接口 Disposable,并在其中使用 Dispose() 方法,将其用作 .NET 的 IDisposable 接口的类比?还是应该重用 Closeable 即使它可能不是一个完美的匹配?

1
@Pharap 在你提供的链接页面上,有两种不同的实现IDisposable的模式。只有在你的对象直接负责分配未托管资源(即未使用SafeHandle包装的本机资源)的相对罕见的情况下,才需要实现Object.Finalize()。你所说的“.NET推荐实现IDisposable的方式[...]需要使用Object.Finalize()`”并不完全正确。 - Daniel Fortunov
3个回答

49

我相信大多数人都已经知道了,但由于在搜索"IDisposable Java"时这个问题仍然是排名靠前的(#2结果),并且这里仍然没有提到...

随着Java 7的推出,有些事情发生了改变: 现在有了AutoCloseable。它不再与流相关,并且受到新的try-with-resources结构的支持。


虽然我觉得Java花了很长时间才获得与IDisposable相当的东西,而且我认为缺乏这样一个结构是Java的一个重大缺陷,但我赞扬Java的创造者们认识到有必要允许try块异常和清理表达式向上传播到调用堆栈。我的个人偏好是让try-with-resources在清理块中发生任何异常时都包装成CleanupFailedException(它将指示在try块中发生了什么异常(如果有的话),以及一系列清理异常)。 - supercat
...并且还提供了一种清理代码的方式,可以告诉它try块是成功还是失败(例如,如果由修改受锁保护的内容的代码引发异常,则该锁通常既不应释放也不应无限期地持有,而应该被使无效,以便任何挂起或将来尝试获取锁的尝试都会立即抛出badLockExitException;如果try-with-resources结构可以让锁定对象自动实现这样的语义,那么这将非常有帮助)。 - supercat
1
我倾向于同意,尽管原因是为了向后兼容,以允许Closeable扩展AutoCloseable。需要考虑的一件事是,如果在主体中抛出异常并且清理也抛出异常,则冒泡异常getSuppressed包含来自close()的异常。 - Brett Ryan

17

特别是由于close()方法可能抛出需要处理的IOException异常,建议您编写自己的接口。该接口可以抛出适用于您要使用接口的目的的任何已检查异常。

在读者心中,接口往往代表意图,因此让一个类实现IO关联的Closeable接口会使读者认为该类也是基于IO的。

显然,如果您想要关闭的对象都与IO相关,则应使用Closeable。但如果不是,则选择其他接口。

/** Interface for objects that require cleanup post-use. Call dispose() in finally block! */
public interface Disposable {
    public void dispose();
}

1
在类中实现Closeable(或者同样适用于AutoClosable)时,也有可能省略throws声明:
class X implements Closeable {
    @Override public void close() /* I don't throw */ {

    }
}

因此,当某人使用已输入的对象时,他们可以调用close()而无需捕获任何内容:

void f() { // notice no need for throws because close() doesn't throw
    X x = new X();
    try {
        // do something
    } finally {
        x.close();
    }
}

它也与任何期望 Closeable 的东西兼容:如果将此对象传递到处理 Closeable 的某个地方,它们已经预期到异常并正确处理它,尽管在这种情况下是徒劳的。

这包括像 Guava Closeables 和 Java 7 try-with-resources 这样的库,正如 Paul Groke 所建议的那样:

try (X x = new X()) {
    // do something
}

不过有一个罕见的注意事项:一旦剥离了异常,就不能在子类中重新引入它:

class Y extends X {
                                  /* compile error */
    @Override public void close() throws IOException {
        // Y.close clashes with X.close: overridden method does not throw IOException
    }
}

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