[英]Java 8 Streams and try with resources
我认为流 API 是为了使代码更易于阅读。 我发现一些很烦人的事情。 Stream
接口扩展了java.lang.AutoCloseable
接口。
所以如果你想正确地关闭你的流,你必须使用 try with resources。
清单 1 。 不是很好,流没有关闭。
public void noTryWithResource() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
@SuppressWarnings("resource") List<ImageView> collect = photos.stream()
.map(photo -> new ImageView(new Image(String.valueOf(photo))))
.collect(Collectors.<ImageView>toList());
}
清单 2 。 使用 2 次嵌套尝试
public void tryWithResource() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
try (Stream<Integer> stream = photos.stream()) {
try (Stream<ImageView> map = stream
.map(photo -> new ImageView(new Image(String.valueOf(photo)))))
{
List<ImageView> collect = map.collect(Collectors.<ImageView>toList());
}
}
}
清单 3 。 当map
返回一个流时, stream()
和map()
函数都必须关闭。
public void tryWithResource2() {
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
try (Stream<Integer> stream = photos.stream(); Stream<ImageView> map = stream.map(photo -> new ImageView(new Image(String.valueOf(photo)))))
{
List<ImageView> collect = map.collect(Collectors.<ImageView>toList());
}
}
我举的例子没有任何意义。 为了这个例子,我用Integer
替换了Path
to jpg 图像。 但是不要让你被这些细节分散注意力。
处理这些可自动关闭的流的最佳方法是什么。 我不得不说我对我展示的 3 个选项中的任何一个都不满意。 你怎么认为? 还有其他更优雅的解决方案吗?
您正在使用@SuppressWarnings("resource")
这可能会抑制有关未关闭资源的警告。 这不是javac
发出的警告之一。 Web 搜索似乎表明如果AutoCloseable
未关闭,Eclipse 会发出警告。
根据引入AutoCloseable
的Java 7 规范,这是一个合理的警告:
不再需要时必须关闭的资源。
但是, AutoCloseable
的Java 8 规范已放宽以删除“必须关闭”子句。 它现在部分说,
一个可能持有资源的对象......直到它被关闭。
即使不是所有的子类或实例都拥有可释放的资源,基类也有可能实现 AutoCloseable,并且实际上很常见。 对于必须完全通用的代码,或者当知道 AutoCloseable 实例需要资源释放时,建议使用 try-with-resources 构造。 但是,当使用 Stream 等同时支持基于 I/O 和非基于 I/O 的形式时,在使用非基于 I/O 的形式时,try-with-resources 块通常是不必要的。
这个问题在 Lambda 专家组内进行了广泛讨论; 该消息总结了该决定。 除其他事项外,它提到了对AutoCloseable
规范(上文引用)和BaseStream
规范(由其他答案引用)的BaseStream
。 它还提到可能需要针对更改的语义调整 Eclipse 代码检查器,大概不会无条件地为AutoCloseable
对象发出警告。 显然这条消息没有传达给 Eclipse 人员,或者他们还没有改变它。
总之,如果 Eclipse 警告使您认为您需要关闭所有AutoCloseable
对象,那是不正确的。 只需要关闭某些特定的AutoCloseable
对象。 Eclipse 需要被修复(如果它还没有)不为所有AutoCloseable
对象发出警告。
如果流需要对自身进行任何清理(通常是 I/O),您只需关闭 Streams。 您的示例使用 HashSet,因此不需要关闭它。
来自Stream javadoc:
通常,只有源是 IO 通道的流(例如 Files.lines(Path, Charset) 返回的流)才需要关闭。 大多数流由集合、数组或生成函数支持,不需要特殊的资源管理。
所以在你的例子中,这应该没有问题
List<ImageView> collect = photos.stream()
.map(photo -> ...)
.collect(toList());
编辑
即使您需要清理资源,您也应该只能使用一个 try-with-resource。 假设您正在读取一个文件,其中文件中的每一行都是图像的路径:
try(Stream<String> lines = Files.lines(file)){
List<ImageView> collect = lines
.map(line -> new ImageView( ImageIO.read(new File(line)))
.collect(toList());
}
“可关闭”的意思是“可以关闭”,而不是“必须关闭”。
过去确实如此,例如参见ByteArrayOutputStream
:
关闭
ByteArrayOutputStream
没有任何效果。
现在对于Stream
s来说, 文档明确说明了这一点:
流有一个
BaseStream.close()
方法并实现了AutoCloseable
,但几乎所有流实例实际上不需要在使用后关闭。 通常,只有源是 IO 通道的流(例如Files.lines(Path, Charset)
返回的Files.lines(Path, Charset)
)才需要关闭。
因此,如果审计工具生成错误警告,则是审计工具的问题,而不是 API 的问题。
注意,即使要添加资源管理,也不需要嵌套try
语句。 虽然以下就足够了:
final Path p = Paths.get(System.getProperty("java.home"), "COPYRIGHT");
try(Stream<String> stream=Files.lines(p, StandardCharsets.ISO_8859_1)) {
System.out.println(stream.filter(s->s.contains("Oracle")).count());
}
您还可以将辅助Stream
添加到资源管理中,而无需额外try
:
final Path p = Paths.get(System.getProperty("java.home"), "COPYRIGHT");
try(Stream<String> stream=Files.lines(p, StandardCharsets.ISO_8859_1);
Stream<String> filtered=stream.filter(s->s.contains("Oracle"))) {
System.out.println(filtered.count());
}
可以创建一个实用方法,使用 try-with-resource-statement 可靠地关闭流。
它有点像 try-finally 是一个表达式(例如 Scala 中的情况)。
/**
* Applies a function to a resource and closes it afterwards.
* @param sup Supplier of the resource that should be closed
* @param op operation that should be performed on the resource before it is closed
* @return The result of calling op.apply on the resource
*/
private static <A extends AutoCloseable, B> B applyAndClose(Callable<A> sup, Function<A, B> op) {
try (A res = sup.call()) {
return op.apply(res);
} catch (RuntimeException exc) {
throw exc;
} catch (Exception exc) {
throw new RuntimeException("Wrapped in applyAndClose", exc);
}
}
(因为需要关闭的资源在被分配时通常也会抛出异常,非运行时异常被包装在运行时异常中,避免了需要一个单独的方法来做到这一点。)
使用此方法,问题中的示例如下所示:
Set<Integer> photos = new HashSet<Integer>(Arrays.asList(1, 2, 3));
List<ImageView> collect = applyAndClose(photos::stream, s -> s
.map(photo -> new ImageView(new Image(String.valueOf(photo))))
.collect(Collectors.toList()));
这在需要关闭流的情况下很有用,例如使用Files.lines
。 当您必须执行“双重关闭”时,它也有帮助,如清单 3中的示例所示。
这个答案是对类似问题的旧答案的改编。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.