我开始了解Java 8 Stream API,我不确定如何向消费者发信号通知流已完成。
在我的例子中,stream-pipeline的结果将分批写入数据库或在消息传递服务上发布。在这种情况下,流管道应该在流关闭后调用一个“刷新”和“关闭”端点的方法。
我对RxJava中实现的 Observable 模式有一点了解,并记住Observer#onComplete
方法用于此目的。
另一方面,Java 8 Consumer
只暴露accept
方法,但无法“关闭”它。在库中挖掘时,我找到了一个名为Consumer
的{{1}}子接口,它提供Sink
方法,但它不公开。最后我想到实现一个end
,它似乎是流中最灵活的消费者,但是没有更简单的选项吗?
答案 0 :(得分:3)
执行最终操作的最简单方法是在流的终端操作之后立即放置适当的语句,例如:
IntStream.range(0, 100).parallel().forEach(System.out::println);
System.out.println("done");
此操作仅在成功的情况下执行,其中提交是适当的。虽然Consumer
以未指定的顺序同时运行,但仍然保证所有这些都在正常返回时完成了它们的工作。
定义在异常情况下也执行的操作并不容易。看看下面的例子:
try(IntStream is=IntStream.range(0, 100).onClose(()->System.out.println("done"))) {
is.parallel().forEach(System.out::println);
}
这与第一个相似,但是如果你使用特殊情况进行测试,例如
try(IntStream is=IntStream.range(0, 100).onClose(()->System.out.println("done"))) {
is.parallel().forEach(x -> {
System.out.println(x);
if(Math.random()>0.7) throw new RuntimeException();
});
}
您可能会在 done
之后遇到数字的打印输出。这适用于特殊情况下的所有类型的清理。当您捕获异常或处理finally
块时,可能仍在运行异步操作。虽然此时在特殊情况下回滚事务没有问题,因为无论如何数据都是不完整的,您必须准备好继续尝试将项目写入现在回滚的资源。
请注意,您所考虑的基于Collector
的解决方案只能为成功完成定义完成操作。所以这些等同于第一个例子;在终端操作之后放置完成语句是Collector
的简单替代。
如果要定义实现项目处理和清理步骤的操作,可以为它创建自己的接口,并将必要的Stream
设置封装到辅助方法中。这是它的样子:
操作界面:
interface IoOperation<T> {
void accept(T item) throws IOException;
/** Called after successfull completion of <em>all</em> items */
default void commit() throws IOException {}
/**
* Called on failure, for parallel streams it must set the consume()
* method into a silent state or handle concurrent invocations in
* some other way
*/
default void rollback() throws IOException {}
}
帮助方法实施:
public static <T> void processAllAtems(Stream<T> s, IoOperation<? super T> c)
throws IOException {
Consumer<IoOperation> rollback=io(IoOperation::rollback);
AtomicBoolean success=new AtomicBoolean();
try(Stream<T> s0=s.onClose(() -> { if(!success.get()) rollback.accept(c); })) {
s0.forEach(io(c));
c.commit();
success.set(true);
}
catch(UncheckedIOException ex) { throw ex.getCause(); }
}
private static <T> Consumer<T> io(IoOperation<T> c) {
return item -> {
try { c.accept(item); }
catch (IOException ex) { throw new UncheckedIOException(ex); }
};
}
使用它而不进行错误处理可能就像
一样简单class PrintNumbers implements IoOperation<Integer> {
public void accept(Integer i) {
System.out.println(i);
}
@Override
public void commit() {
System.out.println("done.");
}
}
processAllAtems(IntStream.range(0, 100).parallel().boxed(), new PrintNumbers());
处理错误是可能的,但正如所说,你必须在这里处理并发。下面的示例也只是打印数字,但使用了一个应该在结尾处关闭的新输出流,因此并发accept
调用必须在特殊情况下处理并发关闭的流。
class WriteNumbers implements IoOperation<Integer> {
private Writer target;
WriteNumbers(Writer writer) {
target=writer;
}
public void accept(Integer i) throws IOException {
try {
final Writer writer = target;
if(writer!=null) writer.append(i+"\n");
//if(Math.random()>0.9) throw new IOException("test trigger");
} catch (IOException ex) {
if(target!=null) throw ex;
}
}
@Override
public void commit() throws IOException {
target.append("done.\n").close();
}
@Override
public void rollback() throws IOException {
System.err.print("rollback");
Writer writer = target;
target=null;
writer.close();
}
}
FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
FileChannel fch = fos.getChannel();
Writer closableStdIO=new OutputStreamWriter(fos);
try {
processAllAtems(IntStream.range(0, 100).parallel().boxed(),
new WriteNumbers(closableStdIO));
} finally {
if(fch.isOpen()) throw new AssertionError();
}
答案 1 :(得分:0)
Java 8流上的终端操作(如collect()
,forEach()
等)将始终完成流。
如果你有从Stream处理对象的东西,你知道当收集器返回时流结束。
如果您只需关闭处理器,可以将其包装在try-with-resource中并在try块内执行终端操作
try(BatchWriter writer = new ....){
MyStream.forEach( o-> writer.write(o));
}//autoclose writer
答案 2 :(得分:0)
您可以使用Stream#onClose(Runnable)指定在关闭流时调用的回调。 Streams是基于拉式的(与基于推送的rx.Observable相反),因此钩子与流相关联,而不是它的消费者
答案 3 :(得分:0)
这有点hack,但效果很好。 创建原始+唯一对象的流连接。
使用peek(),查看是否遇到新对象,然后调用onFinish操作。
使用过滤器返回流,这样就不会返回唯一对象。
这将保留原始流的onClose事件。
MatPaginator
另一种选择是包装原始分隔符,并在返回false时调用该操作。
public static <T> Stream<T> onFinish(Stream<T> stream, Runnable action) {
final Object end = new Object(); // unique object
Stream<Object> withEnd = Stream.concat(stream.sequential(), Stream.of(end));
Stream<Object> withEndAction = withEnd.peek(item -> {
if (item == end) {
action.run();
}
});
Stream<Object> withoutEnd = withEndAction.filter(item -> item != end);
return (Stream<T>) withoutEnd;
}