如何在Java中组合可关闭对象?

如何在Java中组合可关闭对象?,java,Java,我正试图创建一个Java类来管理多个可关闭的资源。C++解决方案将是简单的,并且可以容易地扩展,资源数量也较大: class composed_resource { resource_a a; resource_b b; resource_c c; composed_resource(int x) : a(x), b(x), c(x) { } ~composed_resource() { } }; ComposedRe

我正试图创建一个Java类来管理多个
可关闭的
资源。C++解决方案将是简单的,并且可以容易地扩展,资源数量也较大:

class composed_resource
{
    resource_a a;
    resource_b b;
    resource_c c;

    composed_resource(int x)
        : a(x), b(x), c(x)
    { }

    ~composed_resource()
    { }
};
ComposedResource c = new ComposedResource();
c.add(new Resource1());
c.add(new Resource2());
c.add(new Resource3());
...
// do nice thinks
// and, to close;
c.close();
我的朴素Java解决方案:

public class ComposedResource implements Closeable
{
    private final ResourceA a;
    private final ResourceB b;
    private final ResourceC c;

    public ComposedResource(int x) /* throws ... */ {
        a = new ResourceA(x);
        try {
            b = new ResourceB(x);
            try {
                c = new ResourceC(x);
            } catch (Throwable t) {
                b.close();
                throw t;
            }
        } catch (Throwable t) {
            a.close();
            throw t;
        }
    }

    @Override
    public void close() throws IOException {
        try {
            a.close();
        } finally {
            try {
                b.close();
            } finally {
                c.close();
            }
        }
    }
}
稍微改进的版本:

public class ComposedResource2 implements Closeable
{
    private final ResourceA a;
    private final ResourceB b;
    private final ResourceC c;

    public ComposedResource2(int x) /* throws ... */ {
        try {
            a = new ResourceA(x);
            b = new ResourceB(x);
            c = new ResourceC(x);
        } catch (Throwable t) {
            close();
            throw t;
        }
    }

    @Override
    public void close() throws IOException {
        try {
            if (a != null) a.close();
        } finally {
            try {
                if (b != null) b.close();
            } finally {
                if (c != null) c.close();
            }
        }
    }
}
是否有一种更优雅的解决方案,可以在保持异常安全的同时避免嵌套的try-catch块?有三种资源是可以管理的,但更多的资源变得难以管理。(如果是本地范围,我可以使用“try with resources”语句,但这在这里不适用。)


我在使用
java.rmi
时考虑过这一点。在构造函数中,我创建/查找注册表,查找对象并导出对象。close()需要注销和取消导出对象。我想到创建包装对象来处理导出/非导出(就像我在C++中利用RAII一样),但是我注意到这对我没有多大帮助(我不是那么多的java专家,但我必须用它来上大学)。
目前,我正在使用类似上面的
ComposedResource2
之类的东西,而且效果很好。但现在我想知道是否有更优雅的解决方案。

这样更改close()如何

@Override
public void close() {
   close(a);
   close(b);
   close(c);
}

public void close(Closeable closeable) throws IOException{
       if (closeable != null){
            closeable.close();
       }
}
我认为它更干净

此外,您还可以将这3个资源作为“Closeables”进行管理,并将它们放在一个数组中,这样ComposedResource将拥有您想要的任意多的资源。这样做:

public class ComposedResource{
    List<Closeable> resources = new ArrayList<Closeable>();

    public void add(Closeable c){
        resources.add(c);
    }

    @Override
    public void close(){
        for (Closeable r : resources){
          close(r);
        }
    }

    public void close(Closeable c){
        try{
           c.close();
        catch (IOException e){
           log(e);
        }
    }
}
编辑:

@Mohit Kanwar建议以这种方式抛出异常:

@Override
public void close() throws IOException {
     for (Closeable r : resources){
         r.close(r);
     }
 }

并编辑了我的代码,@Lii说,执行这一异常将阻止关闭所有资源,我同意Lii的意见,因此我拒绝了Mohit的编辑…

使用类似的资源进行尝试

@Override
public void close() throws IOException {
    try (Closeable cc = c;
         Closeable bb = b;
         Closeable aa = a;) {
        // do nothing
    }
}

你能介绍一个用例吗?也许使用一个具体的例子会比抽象的问题产生更好的答案…为什么不尝试使用资源不适用?1)避免在构造函数中创建资源——理想情况下,它们应该只为字段分配参数,我认为RAII在这里不适用。2) 如果可能,请使用try with resources(当构造函数仅在“赋值模式”下工作时,可能对您更可能)。3) 您可以编写
Closeable
装饰器,将多个
Closeable
实例连接到一个实例中。4) 或者你可以使用番石榴
com.google.common.io.Closer
@AdamMichalik:我已经添加了一些细节,但如果可能的话,我想抽象地讨论一下。在我看来,如果这些是文件、套接字或其他东西,这无关紧要——如果可以创建并关闭它们,那么您必须以这样的方式进行处理,以避免资源泄漏(无论是文件句柄,还是开放服务器套接字等)。此外,如果您对静态方法很在行,并且您认为装饰器是一种过火的工具(我个人认为这是出于几个原因,尽管我在上面提出过):如果资源未关闭,我们不应该在日志记录后抛出异常e吗?在您的解决方案中,如果第一个资源关闭时抛出,则后续资源将不会关闭。这不是它应该如何工作的!@inigoD我现在明白了。我为错误的编辑感到抱歉。我已经为此升起了一个标志,因为它花费了您一个downvo再次抱歉:(保存所有抛出的异常,然后在关闭所有资源后抛出它们可能是个好主意。否则错误可能会被隐藏。这就是该方法的目的。我相信这就是try with resource语句的工作方式。@indigoD不知怎的,我所做的一次编辑仍然在您的答案中(第一个代码组件)也请重新编辑它。如果资源由于初始化错误而为null会怎样?@AdamSkywalker如果b为null,a和c将被关闭。@AdamSkywalker
try with resources
在Java中是空安全的:即使所有
cc
bb
aa
都为null,也不会失败。此外,未初始化或初始化不好的对象必须是空的不能使用,并引发初始化异常。最好的情况总是初始化良好的对象。@AngraMainyu:父类构造函数在子类中的任何初始值设定项之前被调用。因此,假设您的子类有一个声明并初始化为
private Foo Foo=new Foo()的字段
…突然,如果它重写了一个可以从父类构造函数调用的方法,它必须支持
foo
仍然为null的可能性!这太混乱和违反直觉了,子类所有者很难记住和跟踪它。@saka1029:没错,但通常它只是用documentat处理的ion(即“如果重写此方法,则必须调用超类实现”),这并不是什么大问题(子类所有者了解,在重写方法时,他们并不总是拥有100%的自由度。必须调用超类实现不是一项繁重的要求)