[英]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.