我应该在flatMap中使用try-with-resource来获取基于I/O的流吗?

Mar*_*sch 6 java java-stream

A Stream是AutoCloseable基于I/O的,应该在try-with-resource块中使用.通过插入的基于中间I/O的流flatMap()怎么样?例:

try (var foos = foos()) {
   return foos.flatMap(Foo::bars).toArray(Bar[]::new);
}
Run Code Online (Sandbox Code Playgroud)

与

try (var foos = foos()) {
  return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
      return bars;
    }
  }).toArray(Bar[]::new);
}
Run Code Online (Sandbox Code Playgroud)

该flatMap()文件说:

每个映射的流在其内容放入此流后关闭.

嗯,这是快乐的道路.如果之间发生异常怎么办?那条流是否会保持未封闭状态并可能泄漏资源?那么我是否应该总是使用try-with-resource也用于中间流?

Hol*_*ger 5

像这样的结构没有任何意义

return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
        return bars;
    }
}).toArray(Bar[]::new);
Run Code Online (Sandbox Code Playgroud)

因为这会在流返回给调用者之前关闭流,这使得子流完全无法使用。

事实上,函数的代码不可能确保关闭会发生在函数之外的适当位置。这肯定是 API 设计人员决定您不必这样做的原因,而 Stream 实现将负责。

这也适用于特殊情况。一旦函数将其返回给 Stream,Stream 仍会确保流被关闭:

try {
    IntStream.range(1, 3)
        .flatMap(i -> {
            System.out.println("creating "+i);
            return IntStream.range('a', 'a'+i)
                    .peek(j -> {
                        System.out.println("processing sub "+i+" - "+(char)j);
                        if(j=='b') throw new IllegalStateException();
                    })
                    .onClose(() -> System.out.println("closing "+i));
        })
        .forEach(i -> System.out.println("consuming "+(char)i));
} catch(IllegalStateException ex) {
    System.out.println("caught "+ex);
}
Run Code Online (Sandbox Code Playgroud)
return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
        return bars;
    }
}).toArray(Bar[]::new);
Run Code Online (Sandbox Code Playgroud)

您可以使用条件来查看构造的 Stream 始终是关闭的。对于未处理的外部 Stream 元素,根本不会有 Stream。

对于像.flatMap(Foo::bars)或 之类的 Stream 操作.flatMap(foo -> foo.bars()),您可以假设一旦bars()成功创建并返回一个 Stream,它将被传递给调用者并正确关闭。

一个不同的场景是映射函数,它在流创建之后执行可能失败的操作,例如

.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    anotherOperation(); // Stream is not closed if this throws
    return s;
})
Run Code Online (Sandbox Code Playgroud)

在这种情况下,有必要确保在特殊情况下关闭,但仅限于特殊情况:

.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    try {
        anotherOperation();
    } catch(Throwable t) {
        try(s) { throw t; } // close and do addSuppressed if follow-up error
    }
    return s;
})
Run Code Online (Sandbox Code Playgroud)

但显然,您应该遵循保持 lambda 简单的一般规则,在这种情况下,您不需要这种保护。