以可关闭资源作为累加器的Java收集器

以可关闭资源作为累加器的Java收集器,java,java-stream,collectors,Java,Java Stream,Collectors,假设我正在尝试创建一个收集器,该收集器将数据聚合到一个在使用后必须关闭的资源中。是否有任何方法可以实现类似于收集器中的finally块的功能?在成功的情况下,这可以在finisher方法中完成,但在异常情况下似乎没有调用任何方法 目标是以干净的方式实现如下操作,而不必首先将流收集到内存列表中 stream.collect(groupingBy(this::extractFileName, collectToFile())); 好的,我已经了解了收集器的实现,您需要收集器mpl来创建自定义收集器

假设我正在尝试创建一个收集器,该收集器将数据聚合到一个在使用后必须关闭的资源中。是否有任何方法可以实现类似于
收集器中的
finally
块的功能?在成功的情况下,这可以在
finisher
方法中完成,但在异常情况下似乎没有调用任何方法

目标是以干净的方式实现如下操作,而不必首先将流收集到内存列表中

stream.collect(groupingBy(this::extractFileName, collectToFile()));

好的,我已经了解了
收集器的实现,您需要
收集器mpl
来创建自定义收集器,但它不是公共的。因此,我使用它的副本(您可能感兴趣的最后2种方法)实现了新的方法:

输出:

Convertingx.txt
closing filex.txt
Convertingy.txt
closing filey.txt
Convertingz.txt
closing filez.txt

我认为您能够满足需求的唯一方法是通过向该方法提供一个紧密的处理程序。假设您有以下类:

class CloseHandler implements Runnable {
    List<Runnable> children = new ArrayList<>();

    void add(Runnable ch) { children.add(ch); }

    @Override
    public void run() { children.forEach(Runnable::run); }
}
因此,这使用了一个本地类(名为
Acc
)来包装可关闭资源,并声明了将流的一个元素添加到可关闭资源的
方法,以及在流并行的情况下合并两个
Acc
实例的

Collector.of
用于基于
Acc
class”方法创建收集器,带有返回
null
的finisher,因为我们不想在由
Collector.groupingBy
创建的映射中放入任何内容

最后,还有
close
方法,如果包装好的可关闭资源已经创建,它将关闭该资源


当通过
try with resources
构造隐式关闭流时,将自动执行
CloseHandler.run
方法,这将依次执行以前在创建每个
Acc
实例时添加的所有子关闭处理程序。

所以您考虑的是
Collector\onError
?如果是这样,没有这样的事情,你必须自己去做…@Eugene Yes,或者可能是一种方法,可以在正在收集的流上干净地附加一个
onClose
方法。@FedericoPeraltaSchaffner我希望每个组都被关闭,异常被传播。在我的示例中,
Collector.supplier()
提供的累加器可以实现
Closeable
,其行为应该像是在try with resources块中调用的一样。@federicoperataschaffner任何选中的异常都必须包装成未选中的异常,如
UncheckedIOException
。但我认为这个问题比文件io更一般。无论是成功完成还是出现异常,我都想关闭资源。再看一些,我可能会包装所有的收集器函数来实现这一点,但在流处理本身中仍然存在任何异常的边缘情况。“我不认为有任何保证这些东西永远不会被扔掉。”费德里科·奥普拉塔沙夫纳这实际上是我在评论中的第一个想法(删除了它,因为它看起来太傻了:|)。实际上,您可以包装收集器中的每个方法,但我怀疑的是,尽管某些线程捕获了异常,而另一些线程正在/将计划写入同一个文件。这些线程需要以某种方式进行通信,我认为OP将提供他想要如何处理此类情况的详细信息。这与我想要实现的不同,目标是根据一些分组键将流的内容写入不同的文件。这需要在某个时刻关闭
FileOutputStream
,即使在异常情况下也是如此。顺便说一句,你可以使用
Collector.of
作为工厂方法,而不是自己实现接口。@JörnHorstmann谢谢Collector.of真的很酷我不知道
merge
是阻止我发布类似内容的主要原因。。。除非OP提供了ThreadA抛出异常(在
合并
中)并且ThreadB想要写入同一文件时具体发生什么的详细信息,否则甚至不能确定这是要求的一部分。。。不必首先将流收集到内存中的列表中,因此似乎存在一些IO/DB/JMS之类的内容。如果OP确认我很乐意向btwThanks投票,那么一个可变的
onClose
回调的组合可以很好地解决这个问题,每个累加器都可以注册。目前我并不关心并行处理,合并操作是否可行(并且比顺序处理更快)在很大程度上取决于资源。实际上,接下来的问题是,序列流是否保证永远不会调用收集器的组合器方法。@JörnHorstmann如果规范保证,请检查。
Convertingx.txt
closing filex.txt
Convertingy.txt
closing filey.txt
Convertingz.txt
closing filez.txt
class CloseHandler implements Runnable {
    List<Runnable> children = new ArrayList<>();

    void add(Runnable ch) { children.add(ch); }

    @Override
    public void run() { children.forEach(Runnable::run); }
}
CloseHandler closeAll = new CloseHandler();
try (Stream<Something> stream = list.stream().onClose(closeAll)) {
    // Now collect
    stream.collect(Collectors.groupingBy(
        this::extractFileName, 
        toFile(closeAll)));
}
static Collector<Something, ?, Void> toFile(CloseHandler closeAll) {

    class Acc {

        SomeResource resource; // this is your closeable resource

        Acc() {
            try {
                resource = new SomeResource(...); // create closeable resource
                closeAll.add(this::close);        // this::close is a Runnable
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        void add(Something elem) {
            try {
                // TODO write/send to closeable resource here
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

        Acc merge(Acc another) {
            // TODO left as an exercise
        }

        // This is the close handler for this particular closeable resource
        private void close() {
            try {
                // Here we close our closeable resource
                if (resource != null) resource.close();
            } catch (IOException ignored) {
            }
        }
    }
    return Collector.of(Acc::new, Acc::add, Acc::merge, a -> null);
}