Java 8 Streams并尝试使用资源

时间:2014-09-11 20:12:29

标签: java eclipse java-8 java-stream

我认为流API在这里使代码更容易阅读。 我发现了一些很烦人的东西。 Stream界面(java.util.stream.Stream)扩展了AutoClosable界面(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());
    }
}

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

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

4 个答案:

答案 0 :(得分:27)

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

根据引入AutoCloseable的{​​{3}}:

,这是一个合理的警告
  

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

然而,AutoCloseable的{​​{3}}已放宽以删除&#34;必须关闭&#34;条款。它现在部分地说,

  

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

     

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

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

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

答案 1 :(得分:12)

如果流需要对其自身进行任何清理(通常是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());
  }

答案 2 :(得分:8)

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

过去是这样,例如见ByteArrayOutputStream

  

关闭ByteArrayOutputStream无效。

现在对Stream

BaseStream.close()来说也是如此
  

Streams有一个AutoCloseable方法并实现Files.lines(Path, Charset),但几乎所有的流实例实际上都不需要在使用后关闭。通常,只有源为IO通道的流(例如try返回的流)才需要关闭。

因此,如果审计工具生成错误警告,则是审计工具的问题,而不是API的问题。

请注意,即使您要添加资源管理,也不需要嵌套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()); }

{{1}}

答案 3 :(得分:3)

可以使用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 中的示例所示。

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