【问题标题】:Java 8 Streams and try with resourcesJava 8 Streams 并尝试使用资源
【发布时间】:2014-11-05 21:56:50
【问题描述】:

我认为流 API 的存在是为了让代码更易于阅读。 我发现了一件很烦人的事情。 Stream 接口扩展了java.lang.AutoCloseable 接口。

所以如果你想正确关闭你的流,你必须对资源使用 try。

清单 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());
    }
}

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

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

【问题讨论】:

标签: java java-8 java-stream


【解决方案1】:

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

根据引入AutoCloseableJava 7 specification,这是一个合理的警告:

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

但是,AutoCloseableJava 8 specification 被放宽以删除“必须关闭”子句。它现在部分表示,

一个可能持有资源的对象......直到它被关闭。

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

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

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

【讨论】:

    【解决方案2】:

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

    来自Streamjavadoc:

    通常,只有源为 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());
      }
    

    【讨论】:

    • 这是我的观点。 Set 不需要关闭。但是 API 需要(某种程度上)将其关闭。
    【解决方案3】:

    “Closeable”的意思是“可以关闭”,而不是“必须关闭”。

    过去确实如此,例如见ByteArrayOutputStream:

    关闭ByteArrayOutputStream 无效。

    现在Streams 也是如此,其中documentation makes clear

    流有一个BaseStream.close() 方法并实现AutoCloseable,但几乎所有流实例在使用后实际上都不需要关闭。通常,只有源为 IO 通道的流(例如 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());
    }
    

    【讨论】:

      【解决方案4】:

      可以创建一个实用方法,使用 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 对类似问题的改编。

      【讨论】:

        猜你喜欢
        • 2020-03-05
        • 2019-08-14
        • 1970-01-01
        • 1970-01-01
        • 2017-02-08
        • 2018-04-20
        • 1970-01-01
        • 1970-01-01
        • 2017-01-03
        相关资源
        最近更新 更多