繁体   English   中英

Java 8 Streams 并尝试使用资源

[英]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 会发出警告。

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

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

但是, AutoCloseableJava 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM