Java “注册流”;“完成”;钩
使用Java 8Java “注册流”;“完成”;钩,java,java-8,java-stream,Java,Java 8,Java Stream,使用Java 8StreamAPI,我想注册一个“完成挂钩”,大致如下: Stream<String> stream = Stream.of("a", "b", "c"); // additional filters / mappings that I don't control stream.onComplete((Completion c) -> { // This is what I'd like to do: closeResources();
Stream
API,我想注册一个“完成挂钩”,大致如下:
Stream<String> stream = Stream.of("a", "b", "c");
// additional filters / mappings that I don't control
stream.onComplete((Completion c) -> {
// This is what I'd like to do:
closeResources();
// This might also be useful:
Optional<Throwable> exception = c.exception();
exception.ifPresent(e -> throw new ExceptionWrapper(e));
});
而不是当前需要的:
try (Stream<X> meh = Utility.something()
.niceLookingSQLDSL()
.moreDSLFeatures()
.stream()) {
Collected collectedWithUglySyntacticDissonance =
meh.filter(a -> true)
.map(c -> c)
.collect(collector);
}
使用现有的JDK 8 API有没有一种简单的方法可以做到这一点?我提出的解决方案如下所示:
class AutoClosingStream<T> implements Stream<T> {
AutoClosingStream(Stream<T> delegate, Consumer<Optional<Throwable>> onComplete) {}
// Pipeline ops delegate the op to the real stream and wrap that again
@Override
public Stream<T> limit(long maxSize) {
return new AutoClosingStream(delegate.limit(maxSize), onComplete);
}
// Terminal ops intercept the result and call the onComplete logic
@Override
public void forEach(Consumer<? super T> action) {
terminalOp(() -> delegate.forEach(action));
}
private void terminalOp(Runnable runnable) {
terminalOp(() -> { runnable.run(); return null; });
}
private <R> R terminalOp(Supplier<R> supplier) {
R result = null;
try {
result = supplier.get();
onComplete.accept(Optional.empty());
}
catch (Throwable e) {
onComplete.accept(Optional.of(e));
Utils.sneakyThrow(e);
}
return result;
}
}
类AutoClosingStream实现流{
AutoClosingStream(流委托,使用者onComplete){}
//管道操作将操作委托给真实流,并再次包装该流
@凌驾
公共流限制(长最大大小){
返回新的AutoClosingStream(委托限制(maxSize),onComplete);
}
//终端ops截取结果并调用onComplete逻辑
@凌驾
public void forEach(Consumer最简单的解决方案是将一个流包装到另一个流中,并将其平面映射到自身:
// example stream
Stream<String> original=Stream.of("bla").onClose(()->System.out.println("close action"));
// this is the trick
Stream<String> autoClosed=Stream.of(original).flatMap(Function.identity());
//example op
int sum=autoClosed.mapToInt(String::length).sum();
System.out.println(sum);
//示例流
Stream original=Stream.of(“bla”).onClose(()->System.out.println(“close action”);
//这就是诀窍
Stream autoClosed=Stream.of(original).flatMap(Function.identity());
//示例op
int sum=autoClosed.mapToInt(字符串::长度).sum();
系统输出打印项数(总和);
其有效的原因在于:
每个映射流在其内容放入该流后关闭
但是,在Java10中已经修复了这个问题
我的建议是继续使用try(…)
需要关闭返回流时的标准解决方案和文档。毕竟,在终端操作后关闭资源的流是不安全的,因为无法保证客户端将实际调用终端操作。改变主意并放弃流是有效的,而不调用close()
方法,当文档指定它是必需的时,它不是。Java 8已经有了需要关闭的流如何运行的先例。在它们的示例中,它提到:
Streams有一个BaseStream.close()方法并实现自动关闭,但几乎所有流实例在使用后实际上都不需要关闭。通常,只有源为IO通道的流(例如由Files.lines(Path,Charset)返回的流)将需要关闭。大多数流由集合、数组或生成函数支持,不需要特殊的资源管理。(如果流确实需要关闭,则可以在try-with-resources语句中将其声明为资源。)
所以Java8的建议是在尝试使用资源时打开这些流。一旦您这样做,Stream
还为您提供了一种添加close hook的方法,几乎完全如您所述:,它接受lambda,告诉它做什么,并返回一个Stream
,该流在关闭时也会执行该操作
这就是API设计和文档建议您尝试执行的操作。任何拦截终端操作的解决方案(基于flatMap
的解决方案除外)(由@Holger提出)都会容易受到以下代码的攻击:
Stream<String> stream = getAutoCloseableStream();
if(stream.iterator().hasNext()) {
// do something if stream is non-empty
}
这会在内部对第一个流使用spliterator().tryAdvance()
,然后放弃它(尽管在显式调用结果流close()
时关闭)。您需要要求用户也不要使用stream.concat
。据我所知,在您的库内部您正在使用iterator()
/spliterator()
非常频繁,因此您需要重新访问所有这些地方以查找可能出现的问题。当然,还有许多其他库也使用了iterator()
/spliterator()
,并且可能在这之后短路:所有这些库都将与您的功能不兼容
为什么基于flatMap
的解决方案在这里工作?因为在第一次调用hasNext()
或tryAdvance()时
它将整个流内容转储到中间缓冲区,并关闭原始流源。因此,根据流大小,您可能会浪费大量中间内存,甚至出现OutOfMemoryError
您还可以考虑将<代码>幻像引用< /代码> s保存到<代码>流>代码>对象,并监视<代码>参考代码>代码>。在这种情况下,完成将由垃圾回收器触发(这也有一些缺点)。
总之,我的建议是继续使用try with resources。以开源项目的速度查看AutoClosingReferenceStream、AutoClosingTStream、AutoClosingLongStream和AutoClosingDoubleStream的完整实现
该解决方案与@LukasEder提到的类似,因此您必须覆盖/实现流
接口的所有>30个方法(四次)只要客户端使用即将推出的Java 9default
方法之一,它就会崩溃。对我来说,这听起来不是一个解决方案。@霍尔格:我不担心实现4x30方法。我知道JDK 9的限制。但这些新方法不会让人大吃一惊。目前,有,我今天已经可以实现了,并且可以委托使用反射(或自己实现)。不过,我同意,这方面可能与未来JDK版本向后不兼容是一个缺点。请注意,这里完全不需要偷偷抛出。您只需使用catch(Throwable e){/*some action*/throw e;}
Hint~@Lukas Eder:Java 7中引入了异常处理改进,以及try(…)
语句、多捕获等。这就是为什么变量“有效最终”的概念在Java 8中并不新鲜,它已经是这个Java 7构造所必需的。非常有趣的发现!“因为不能保证客户机将实际调用终端操作”-事实上,但是资源的打开可能会延迟到调用终端操作。对,因为实际用例将通过StreamSu
// example stream
Stream<String> original=Stream.of("bla").onClose(()->System.out.println("close action"));
// this is the trick
Stream<String> autoClosed=Stream.of(original).flatMap(Function.identity());
//example op
int sum=autoClosed.mapToInt(String::length).sum();
System.out.println(sum);
Stream<String> stream = getAutoCloseableStream();
if(stream.iterator().hasNext()) {
// do something if stream is non-empty
}
Stream<String> stream = getAutoCloseableStream();
Stream.concat(stream, Stream.of("xyz")).findFirst();