[英]Java 8 equivalent of (RxJava) Observable#onComplete()
我逐渐了解Java 8 Stream API,并且不确定如何向流的使用者发送信号,告知该流已完成。
就我而言,流管道的结果将成批写入数据库或在消息传递服务上发布。 在这种情况下,一旦关闭流,流管道应调用一种方法来“刷新”和“关闭”端点。
我对RxJava中实现的Observable模式有一些了解,并记得在其中使用了Observer#onComplete
方法。
另一方面,Java 8 Consumer
仅公开了accept
方法,而没有“关闭”方法。 在库中进行挖掘时,我发现了一个名为Sink
的Consumer
子接口,它提供了一种end
方法,但它不是公开的。 最后,我想到了实现一个似乎是流中最灵活的使用者的Collector
,但是没有任何更简单的选择吗?
完成最终操作的最简单方法是在流的终端操作之后放置适当的语句,例如:
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();
}
Java 8流上的终端操作(例如collect()
, forEach()
等)将始终完成该流。
如果您正在处理流中的对象,则知道收集器返回时流结束。
如果只需要关闭处理器,则可以将其包装在try-with-resource中,并在try块内执行终端操作
try(BatchWriter writer = new ....){
MyStream.forEach( o-> writer.write(o));
}//autoclose writer
您可以使用Stream#onClose(Runnable)指定在关闭流时要调用的回调。 流是基于拉的(与基于推的rx.Observable相反),因此钩子与流相关联,而不是它的使用者
这有点hack,但效果很好。 创建原始+唯一对象的流连接。
使用peek(),查看是否遇到新对象,然后调用onFinish操作。
使用过滤器返回流,这样就不会返回唯一对象。
这将保留原始流的onClose事件。
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;
}
另一种选择是包装原始分隔符,并在返回false时调用该操作。
public static <T> Stream<T> onFinishWithSpliterator(Stream<T> source, Runnable onFinishAction) {
Spliterator<T> spliterator = source.spliterator();
Spliterator<T> result = new Spliterators.AbstractSpliterator<T>(source.estimateSize(), source.characteristics()) {
@Override
public boolean tryAdvance(Consumer<? super T> action) {
boolean didAdvance = source.tryAdvance(action);
if (!didAdvance) {
onFinishAction.run();
}
return didAdvance;
}
};
// wrap the the new spliterator with a stream and keep the onClose event
return StreamSupport.stream(result, false).onClose(source::close);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.