繁体   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