当前位置: 代码迷 >> java >> 我应该在flatMap中使用try-with-resource来获取基于I / O的流吗?
  详细解决方案

我应该在flatMap中使用try-with-resource来获取基于I / O的流吗?

热度:17   发布时间:2023-08-02 11:15:48.0

StreamAutoCloseable ,如果基于I / O,则应在try-with-resource块中使用。 那么通过flatMap()插入的基于中间I / O的流怎么样? 例:

try (var foos = foos()) {
   return foos.flatMap(Foo::bars).toArray(Bar[]::new);
}

try (var foos = foos()) {
  return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
      return bars;
    }
  }).toArray(Bar[]::new);
}

flatMap()文档说:

在将其内容放入此流后,每个映射的流都将关闭。

嗯,这是快乐的道路。 如果两者之间发生异常怎么办? 那条流是否会保持未封闭状态并可能泄漏资源? 那么我是否应该始终对中间流使用try-with-resource

在像这样的结构中没有任何意义

return foos.flatMap(foo -> {
    try (var bars = foo.bars()) {
        return bars;
    }
}).toArray(Bar[]::new);

因为这将在流返回到调用者之前关闭流,这使得子流完全不可用。

事实上,函数的代码不可能确保关闭将发生在函数之外的适当位置。 这肯定是API设计者决定您不必的原因,而Stream实现将会照顾。

这也适用于特殊情况。 一旦函数将流返回到Stream,Stream仍然确保流关闭:

try {
    IntStream.range(1, 3)
        .flatMap(i -> {
            System.out.println("creating "+i);
            return IntStream.range('a', 'a'+i)
                    .peek(j -> {
                        System.out.println("processing sub "+i+" - "+(char)j);
                        if(j=='b') throw new IllegalStateException();
                    })
                    .onClose(() -> System.out.println("closing "+i));
        })
        .forEach(i -> System.out.println("consuming "+(char)i));
} catch(IllegalStateException ex) {
    System.out.println("caught "+ex);
}
creating 1
processing sub 1 - a
consuming a
closing 1
creating 2
processing sub 2 - a
consuming a
processing sub 2 - b
closing 2
caught java.lang.IllegalStateException

您可以使用条件,以查看构造的Stream始终关闭。 对于未经过处理的外部流的元素,根本就没有Stream。

对于像.flatMap(Foo::bars).flatMap(foo -> foo.bars())这样的Stream操作,你可以假设一旦bars()成功创建并返回了一个Stream,它就会传递给调用者确定并正确关闭。

不同的场景是映射函数,这些函数在流创建之后执行可能失败的操作,例如

.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    anotherOperation(); // Stream is not closed if this throws
    return s;
})

在这种情况下,有必要确保特殊情况下的结束,但仅限于特殊情况:

.flatMap(foo -> {
    Stream<Type> s = foo.bar();
    try {
        anotherOperation();
    } catch(Throwable t) {
        try(s) { throw t; } // close and do addSuppressed if follow-up error
    }
    return s;
})

但显然,你应该遵循一般规则来保持lambda简单,在这种情况下你不需要这样的保护。

在Stream中,您必须关闭相关位置的IO资源。
flatMap()方法是一种通用流方法,因此它不知道您在其中打开的IO资源。
但是为什么flatMap()行为与操作IO资源的方法不同? 例如,如果您在map()操作IO,则在发生异常时可能会遇到相同的问题(没有释放资源)。
关闭流(如在flatMap() )将不会释放它在流操作中打开的所有资源。
一些方法可以做到这一点,例如File.lines(Path) 但是如果你在flatMap()打开一些资源,当关闭流时,这些资源的关闭将不会自动完成。
例如,flatMap处理不会关闭打开的FileInputStream

 ...
 .stream()
 .flatMap(foo -> {
    try {
       FileInputStream fileInputStream = new FileInputStream("..."));                                  
       //...
     }
     catch (IOException e) {
         // handle
     }

 })

你必须明确地关闭它:

 ...
 .stream()
 .flatMap(foo -> {
     try (FileInputStream fileInputStream = new FileInputStream("...")){
         //...
     } catch (IOException e) {
         // handle
     }
    // return
 })

所以,如果在flatMap()内部使用的语句或任何方法操作某些IO资源,您希望在任何情况下通过使用try-with-resources语句将它们包围以使它们空闲来关闭它们。

  相关解决方案