我认为流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个选项中的任何一个都不满意。 你怎么看?还有其他更优雅的解决方案吗?
答案 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对类似问题的改编。