问题描述
Stream
是AutoCloseable
,如果基于I / O,则应在try-with-resource
块中使用。
那么通过flatMap()
插入的基于中间I / O的流怎么样?
例:
try (var foos = foos()) {
return foos.flatMap(Foo::bars).toArray(Bar[]::new);
}
与
try (var foos = foos()) {
return foos.flatMap(foo -> {
try (var bars = foo.bars()) {
return bars;
}
}).toArray(Bar[]::new);
}
flatMap()
文档说:
在将其内容放入此流后,每个映射的流都将关闭。
嗯,这是快乐的道路。
如果两者之间发生异常怎么办?
那条流是否会保持未封闭状态并可能泄漏资源?
那么我是否应该始终对中间流使用try-with-resource
?
1楼
在像这样的结构中没有任何意义
return foos.flatMap(foo -> {
try (var bars = foo.bars()) {
return bars;
}
}).toArray(Bar[]::new);
因为这将在流返回到调用者之前关闭流,这使得子流完全不可用。
事实上,函数的代码不可能确保关闭将发生在函数之外的适当位置。 这肯定是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);
}
creating 1
processing sub 1 - a
consuming a
closing 1
creating 2
processing sub 2 - a
consuming a
processing sub 2 - b
closing 2
caught java.lang.IllegalStateException
您可以使用条件,以查看构造的Stream始终关闭。 对于未经过处理的外部流的元素,根本就没有Stream。
对于像.flatMap(Foo::bars)
或.flatMap(foo -> foo.bars())
这样的Stream操作,你可以假设一旦bars()
成功创建并返回了一个Stream,它就会传递给调用者确定并正确关闭。
不同的场景是映射函数,这些函数在流创建之后执行可能失败的操作,例如
.flatMap(foo -> {
Stream<Type> s = foo.bar();
anotherOperation(); // Stream is not closed if this throws
return s;
})
在这种情况下,有必要确保特殊情况下的结束,但仅限于特殊情况:
.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;
})
但显然,你应该遵循一般规则来保持lambda简单,在这种情况下你不需要这样的保护。
2楼
在Stream中,您必须关闭相关位置的IO资源。
flatMap()
方法是一种通用流方法,因此它不知道您在其中打开的IO资源。
但是为什么flatMap()
行为与操作IO资源的方法不同?
例如,如果您在map()
操作IO,则在发生异常时可能会遇到相同的问题(没有释放资源)。
关闭流(如在flatMap()
)将不会释放它在流操作中打开的所有资源。
一些方法可以做到这一点,例如File.lines(Path)
。
但是如果你在flatMap()
打开一些资源,当关闭流时,这些资源的关闭将不会自动完成。
例如,flatMap处理不会关闭打开的FileInputStream
:
...
.stream()
.flatMap(foo -> {
try {
FileInputStream fileInputStream = new FileInputStream("..."));
//...
}
catch (IOException e) {
// handle
}
})
你必须明确地关闭它:
...
.stream()
.flatMap(foo -> {
try (FileInputStream fileInputStream = new FileInputStream("...")){
//...
} catch (IOException e) {
// handle
}
// return
})
所以,如果在flatMap()
内部使用的语句或任何方法操作某些IO资源,您希望在任何情况下通过使用try-with-resources
语句将它们包围以使它们空闲来关闭它们。