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()); } }
我给出的例子没有任何意义。 为了示例,我使用Integer
将Path
替换为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中的示例所示 。
这个答案是对类似问题的旧答案的改编。