簡體   English   中英

相當於(RxJava)Observable#onComplete()的Java 8

[英]Java 8 equivalent of (RxJava) Observable#onComplete()

我逐漸了解Java 8 Stream API,並且不確定如何向流的使用者發送信號,告知該流已完成。

就我而言,流管道的結果將成批寫入數據庫或在消息傳遞服務上發布。 在這種情況下,一旦關閉流,流管道應調用一種方法來“刷新”和“關閉”端點。

我對RxJava中實現的Observable模式有一些了解,並記得在其中使用了Observer#onComplete方法。

另一方面,Java 8 Consumer僅公開了accept方法,而沒有“關閉”方法。 在庫中進行挖掘時,我發現了一個名為SinkConsumer子接口,它提供了一種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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM