【问题标题】:Exception-safely return Autoclosable object异常安全地返回 Autoclosable 对象
【发布时间】:2019-08-22 15:05:56
【问题描述】:

当你想使用一些AutoClosable 对象时,你应该使用try-with-resources。好的。但是,如果我想编写返回AutoClosable 的方法怎么办?在您从某个地方创建或接收 AutoCloseable 对象后,您应该在出现异常时关闭它,如下所示:

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some AutoClosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            autoCloseable.close();
            throw exception;
        }
        return autoCloseable;
    }

如果你不写try/catch 块你会泄漏资源,autoCloseable 对象持有,以防// some work 行出现异常。 但是这个try/catch 是不够的,因为autoCloseable.close() 也可以抛出异常(按设计)。因此,上面的代码转换为

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some autoclosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            try {
                autoCloseable.close();
            }
            catch (Throwable exceptionInClose) {
                exception.addSuppressed(exceptionInClose);
                throw exception;
            }
            throw exception;
        }
        return autoCloseable;
    }

这是很多样板。有没有更好的方法在java中做到这一点?

【问题讨论】:

  • 问题是你为什么要返回autoclosable?,因为它已经在返回语句中关闭了。
  • @Kiskae 为什么会在上面示例代码中的 return 语句处被关闭?

标签: java autocloseable


【解决方案1】:

有很多人接洽。

  • 使用Execute Around idiom。重新设计接口以简化客户端实施并消除问题。
  • 忽略问题。听起来很傻,但这通常是使用 I/O 流装饰器包装时发生的情况。
  • 包装在代理 AutoCloseable 中并将其放入您的 try-with-resource 中。
  • 写出等价的 try-with-resource 使用 try-catch 和 try-finally。 (我不会 - 讨厌。)
  • 将修改后的 try-with-resource 写入库功能。此代码将成为 Execute Around 的客户端。
  • 使用 try-catch 和 try-finally 编写老式的异常处理方式。

编辑:我想我会重新审视答案,添加一些示例代码来娱乐。

Execute Around 成语

简单且最佳的解决方案。不幸的是,Java 库并没有大量使用它(AccessController.doPrivileged 是一个很大的例外),并且没有很好地建立约定。与以往一样,没有支持特性的 Java 已检查异常使事情变得棘手。我们不能使用java.util.function,必须发明我们自己的功能接口。

// Like Consumer, but with an exception.
interface Use<R, EXC extends Exception> {
    void use(R resource) throws EXC;
}

public static void withThing(String name, Use<InputStream,IOException> use) throws IOException {
     try (InputStream in = new FileInputStream(name)) {
         use.use(in);
     }
}

漂亮而简单。无需担心客户端代码会弄乱资源处理,因为它不会这样做。不错。

修改后的 try-with-resource 作为库功能实现为 try-with-resource 中的代理 AutoCloseable

它会变得丑陋。我们需要将获取、释放和初始化作为 lambdas 传递。直接在此方法中创建资源会打开一个小窗口,其中意外异常会导致泄漏。

public static InputStream newThing(String name) throws IOException {
    return returnResource(
        () -> new FileInputStream(name),
        InputStream::close,
        in -> {
            int ignore = in.read(); // some work
        }
    );
}

returnResource 的一般实现如下所示。一个 hack,因为 try-with-resource 不支持这种事情并且 Java 库不能很好地支持检查的异常。注意仅限于一个异常(您可以使用未经检查的异常来表示没有检查的异常)。

interface Acquire<R, EXC extends Exception> {
    R acquire() throws EXC;
}
// Effectively the same as Use, but different.
interface Release<R, EXC extends Exception> {
    void release(R resource) throws EXC;
}

public static <R, EXC extends Exception> R returnResource(
    Acquire<R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
) throws EXC {
    try (var adapter = new AutoCloseable() { // anonymous classes still define type
        private R resource = acquire.acquire();
        R get() {
            return resource;
        }
        void success() {
            resource = null;;
        }
        public void close() throws EXC {
           if (resource != null) {
               release.release(resource);
           }
        }
    }) {
        R resource = adapter.get();
        initialize.use(resource);
        adapter.success();
        return resource;
    }
}

如果我们将构造资源的参数与构造资源的参数分开,这可能会更清晰。

public static InputStream newThing(String name) throws IOException {
    return returnResource(
        name,
        FileInputStream::new,
        InputStream::close,
        in -> {
            int ignore = in.read(); // some work
        }
    );
}

// Like Function, but with a more descriptive name for a functional interface.
interface AcquireFrom<T, R, EXC extends Exception> {
    R acquire(T t) throws EXC;
}

public static <T, R, EXC extends Exception> R returnResource(
    T t, AcquireFrom<T, R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
 ) throws EXC {
     return returnResource(() -> acquire.acquire(t), release, initialize);
 }

所以总而言之,以下事情是痛苦的:

  • 转移资源所有权。保持本地化。
  • java.util.function 不支持检查异常。
  • Java 库不支持 Execute Around。
  • AutoCloseable::close 声明它抛出 Exception 而不是该类型的类型参数。
  • 没有总和类型的已检查异常。
  • 一般的 Java 语言语法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-05
    • 1970-01-01
    • 2010-10-24
    • 2013-01-23
    • 1970-01-01
    • 2020-10-23
    • 2015-08-11
    • 2017-02-24
    相关资源
    最近更新 更多