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也用于中间流?
像这样的结构没有任何意义
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 简单的一般规则,在这种情况下,您不需要这种保护。