Java 8 Streams并尝试使用资源

我认为流API在这里使代码更容易阅读。 我发现了一些很烦人的东西。 Stream接口( java.util.stream.Stream )扩展了AutoClosable接口( java.lang.AutoCloseable

因此,如果要正确关闭流,则必须使用try with resources。

清单1 。 不太好,溪流没有关闭。

  public void noTryWithResource() { Set photos = new HashSet(Arrays.asList(1, 2, 3)); @SuppressWarnings("resource") List collect = photos.stream() .map(photo -> new ImageView(new Image(String.valueOf(photo)))).collect(Collectors.toList()); } 

清单2 。 2个imbricated尝试:(

  public void tryWithResource() { Set photos = new HashSet(Arrays.asList(1, 2, 3)); try (Stream stream = photos.stream()) { try (Stream map = stream .map(photo -> new ImageView(new Image(String.valueOf(photo))))) { List collect = map.collect(Collectors.toList()); } } } 

清单3 。 当map返回一个流时,必须关闭stream()map()函数。

  public void tryWithResource2() { Set photos = new HashSet(Arrays.asList(1, 2, 3)); try (Stream stream = photos.stream(); Stream map = stream .map(photo -> new ImageView(new Image(String.valueOf(photo))))) { List collect = map.collect(Collectors.toList()); } } 

我给出的例子没有任何意义。 为了示例,我使用IntegerPath替换为jpg图像。 但是不要让你分心这些细节。

使用那些可自动关闭的流的最佳方法是什么。 我不得不说我对我展示的3个选项中的任何一个都不满意。 你怎么看? 还有其他更优雅的解决方案吗?

您正在使用@SuppressWarnings("resource") ,它可能会抑制有关未封闭资源的警告。 这不是javac发出的警告之一。 Web搜索似乎表明如果AutoCloseable未关闭,Eclipse会发出警告。

根据引入AutoCloseable的Java 7规范 ,这是一个合理的警告:

在不再需要时必须关闭的资源。

但是,放宽了AutoCloseable的Java 8规范以删除“必须关闭”子句。 它现在部分地说

一个可以保存资源的对象……直到它被关闭。

基类实现AutoCloseable是可能的,实际上也是常见的,即使并非所有子类或实例都拥有可释放的资源。 对于必须完全通用的代码,或者当已知AutoCloseable实例需要资源释放时,建议使用try-with-resources构造。 但是,当使用支持基于I / O和非I / O的表单的Stream等工具时,在使用非基于I / O的表单时通常不需要try-with-resources块。

Lambda专家组对此问题进行了广泛讨论; 此消息总结了该决定。 除此之外,它还提到了AutoCloseable规范(上面引用的)和BaseStream规范(其他答案引用)的变化。 它还提到可能需要针对更改的语义调整Eclipse代码检查器,可能不会无条件地为AutoCloseable对象发出警告。 显然这条消息没有传达给Eclipse人员,或者他们还没有改变它。

总之,如果Eclipse警告引导您认为您需要关闭所有AutoCloseable对象,那就不正确了。 只需要关闭某些特定的AutoCloseable对象。 需要修复Eclipse(如果尚未修复)不为所有AutoCloseable对象发出警告。

如果流需要对其自身进行任何清理(通常是I / O),则只需关闭Streams。 您的示例使用HashSet,因此不需要关闭它。

来自Stream javadoc:

通常,只有源为IO通道的流(例如Files.lines(Path,Charset)返回的流)才需要关闭。 大多数流都由集合,数组或生成函数支持,不需要特殊的资源管理。

所以在你的例子中,这应该没有问题

 List collect = photos.stream() .map(photo -> ...) .collect(toList()); 

编辑

即使您需要清理资源,您也应该只使用一次try-with-resource。 让我们假装您正在读取文件,其中文件中的每一行都是图像的路径:

  try(Stream lines = Files.lines(file)){ List collect = lines .map(line -> new ImageView( ImageIO.read(new File(line))) .collect(toList()); } 

“可关闭”表示“可以关闭”,而不是“必须关闭”。

这在过去是正确的,例如见ByteArrayOutputStream

关闭ByteArrayOutputStream无效。

对于文档明确说明的 Stream ,现在也是如此:

Streams有一个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 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 stream=Files.lines(p, StandardCharsets.ISO_8859_1); Stream 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  B applyAndClose(Callable sup, Function 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); } } 

(由于需要关闭的资源在分配时经常会抛出exception,因此非运行时exception包含在运行时exception中,从而避免需要单独的方法来执行此操作。)

使用此方法,问题中的示例如下所示:

 Set photos = new HashSet(Arrays.asList(1, 2, 3)); List collect = applyAndClose(photos::stream, s -> s .map(photo -> new ImageView(new Image(String.valueOf(photo)))) .collect(Collectors.toList())); 

这在需要关闭流时(例如使用Files.lines时)非常有用。 当您必须执行“双重关闭”时,它也会有所帮助,如清单3中的示例所示

这个答案是对类似问题的旧答案的改编。