简体   繁体   English

注册流“完成”挂钩

[英]Register a Stream "completion" hook

Using the Java 8 Stream API, I would like to register a "completion hook", along the lines of:使用 Java 8 Stream API,我想注册一个“完成挂钩”,如下所示:

Stream<String> stream = Stream.of("a", "b", "c");

// additional filters / mappings that I don't control
stream.onComplete((Completion c) -> {
    // This is what I'd like to do:
    closeResources();

    // This might also be useful:
    Optional<Throwable> exception = c.exception();
    exception.ifPresent(e -> throw new ExceptionWrapper(e));
});

The reason why I want to do that is because I want to wrap a resource in a Stream for API clients to consume, and I want that Stream to clean up the resource automatically once it is consumed.我想要这样做的原因是因为我想将资源包装在Stream中供 API 客户端使用,并且我希望Stream在资源被使用后自动清理资源。 If that were possible, then the client could call:如果可能的话,那么客户可以调用:

Collected collectedInOneGo =
Utility.something()
       .niceLookingSQLDSL()
       .moreDSLFeatures()
       .stream()
       .filter(a -> true)
       .map(c -> c)
       .collect(collector);

Rather than what's needed currently:而不是当前需要的:

try (Stream<X> meh = Utility.something()
                            .niceLookingSQLDSL()
                            .moreDSLFeatures()
                            .stream()) {

    Collected collectedWithUglySyntacticDissonance =
    meh.filter(a -> true)
       .map(c -> c)
       .collect(collector);
}

Ideally, I'd like to get into the java.util.stream.ReferencePipeline 's various methods, such as:理想情况下,我想深入了解java.util.stream.ReferencePipeline的各种方法,例如:

@Override
final void forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
    try {

        // Existing loop
        do { } while (!sink.cancellationRequested() && spliterator.tryAdvance(sink));
    }

    // These would be nice:
    catch (Throwable t) {
        completion.onFailure(t);
    }
    finally {
        completion.onSuccess();
    }
}

Is there an easy way to do this with existing JDK 8 API?有没有一种简单的方法可以使用现有的 JDK 8 API 来做到这一点?

Any solution intercepting the terminal operations except flatMap -based solution (as proposed by @Holger) would be fragile to the following code:除了基于flatMap的解决方案(由@Holger 提出)之外,任何拦截终端操作的解决方案对于以下代码来说都是脆弱的:

Stream<String> stream = getAutoCloseableStream();
if(stream.iterator().hasNext()) {
    // do something if stream is non-empty
}

Such usage is absolutely legal by the specification.这种用法在规范中是绝对合法的。 Do not forget that iterator() and spliterator() are terminal stream operations, but after their execution you still need an access to the stream source.不要忘记iterator()spliterator()是终端流操作,但在它们执行后您仍然需要访问流源。 Also it's perfectly valid to abandon the Iterator or Spliterator in any state, so you just cannot know whether it will be used further or not.此外,在任何状态下放弃IteratorSpliterator都是完全有效的,因此您无法知道它是否会被进一步使用。

You may consider advicing users not to use iterator() and spliterator() , but what about this code?您可能会考虑建议用户不要使用iterator()spliterator() ,但是这段代码呢?

Stream<String> stream = getAutoCloseableStream();
Stream.concat(stream, Stream.of("xyz")).findFirst();

This internally uses spliterator().tryAdvance() for the first stream, then abandons it (though closes if the resulting stream close() is called explicitly).这在内部使用spliterator().tryAdvance()作为第一个流,然后放弃它(尽管如果显式调用生成的流close()则关闭)。 You will need to ask your users not to use Stream.concat as well.您还需要要求您的用户不要使用Stream.concat And as far as I know internally in your library you are using iterator() / spliterator() pretty often, so you will need to revisit all these places for possible problems.据我所知,在您的库内部,您经常使用iterator() / spliterator() ,因此您需要重新访问所有这些地方以解决可能的问题。 And, of course there are plenty of other libraries which also use iterator() / spliterator() and may short-circuit after that: all of them would become incompatible with your feature.而且,当然还有很多其他库也使用iterator() / spliterator()并可能在那之后短路:所有这些库都会与您的功能不兼容。

Why flatMap -based solution works here?为什么基于flatMap的解决方案在这里有效? Because upon the first call of the hasNext() or tryAdvance() it dumps the entire stream content into the intermediate buffer and closes the original stream source.因为在第一次调用hasNext()tryAdvance()时,它会将整个流内容转储到中间缓冲区并关闭原始流源。 So depending on the stream size you may waste much intermediate memory or even have OutOfMemoryError .因此,根据流的大小,您可能会浪费很多中间内存,甚至会出现OutOfMemoryError

You may also consider keeping the PhantomReference s to the Stream objects and monitoring the ReferenceQueue .您还可以考虑将PhantomReference保存到Stream对象并监视ReferenceQueue In this case the completion will be triggered by garbage collector (which also has some drawbacks).在这种情况下,完成将由垃圾收集器触发(这也有一些缺点)。

In conclusion my advice is to stay with try-with-resources.总之,我的建议是继续使用 try-with-resources。

The simplest solution is to wrap a stream in another stream and flatmap it to itself:最简单的解决方案是将一个流包装在另一个流中并将其平面映射到自身:

// example stream
Stream<String> original=Stream.of("bla").onClose(()->System.out.println("close action"));

// this is the trick
Stream<String> autoClosed=Stream.of(original).flatMap(Function.identity());

//example op
int sum=autoClosed.mapToInt(String::length).sum();
System.out.println(sum);

The reason why it works lies in the flatMap operation :它起作用的原因在于flatMap操作

Each mapped stream is closed after its contents have been placed into this stream.每个映射流在其内容放入此流后关闭。

But the current implementation isn't as lazy as it should be when using flatMap .但是当前的实现并不像使用flatMap时应该的那样懒惰 This has been fixed in Java 10.这已在 Java 10 中修复。


My recommendation is to stay with the try(…) standard solution and document when a returned stream needs to be closed.我的建议是继续使用try(…)标准解决方案,并记录何时需要关闭返回的流。 After all, a stream that closes the resource after the terminal operation isn't safe as there is no guaranty that the client will actually invoke a terminal operation.毕竟,在终端操作后关闭资源的流是不安全的,因为无法保证客户端会实际调用终端操作。 Changing its mind and abandon a stream instead, is a valid use, whereas not calling the close() method, when the documentation specifies that it is required, is not.改变主意并放弃流是一种有效的使用,而当文档指定需要时不调用close()方法则不是。

Java 8 already has a precedent for how streams that need to be closed operate. Java 8 已经有了需要关闭流的操作方式的先例。 In their Javadoc , it mentions:在他们的Javadoc中,它提到:

Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use.流有一个 BaseStream.close() 方法并实现了 AutoCloseable,但几乎所有的流实例实际上并不需要在使用后关闭。 Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing.通常,只有源为 IO 通道的流(例如 Files.lines(Path, Charset) 返回的流)才需要关闭。 Most streams are backed by collections, arrays, or generating functions, which require no special resource management.大多数流由集合、数组或生成函数支持,不需要特殊的资源管理。 (If a stream does require closing, it can be declared as a resource in a try-with-resources statement.) (如果流确实需要关闭,则可以在 try-with-resources 语句中将其声明为资源。)

So Java 8's recommendation is to open those streams in a try-with-resources.所以 Java 8 的建议是在 try-with-resources 中打开这些流。 And once you do that, Stream also provides a way for you to add a close hook, almost exactly as you've described: onClose(Runnable) , which accepts a lambda telling it what to do and returns a Stream that will also do that operation when it is closed.一旦你这样做了, Stream为你提供了一种添加关闭钩子的方法,几乎与你所描述的完全一样: onClose(Runnable) ,它接受一个 lambda 告诉它做什么并返回一个Stream也将这样做关闭时运行。

That's the way the API design and documentation suggests to do what you're trying to do.这就是 API 设计和文档建议的方式来执行您正在尝试执行的操作。

The solution I've come up with looks like this:我提出的解决方案如下所示:

class AutoClosingStream<T> implements Stream<T> {

    AutoClosingStream(Stream<T> delegate, Consumer<Optional<Throwable>> onComplete) {}

    // Pipeline ops delegate the op to the real stream and wrap that again
    @Override
    public Stream<T> limit(long maxSize) {
        return new AutoClosingStream(delegate.limit(maxSize), onComplete);
    }

    // Terminal ops intercept the result and call the onComplete logic
    @Override
    public void forEach(Consumer<? super T> action) {
        terminalOp(() -> delegate.forEach(action));
    }

    private void terminalOp(Runnable runnable) {
        terminalOp(() -> { runnable.run(); return null; });
    }

    private <R> R terminalOp(Supplier<R> supplier) {
        R result = null;

        try {
            result = supplier.get();
            onComplete.accept(Optional.empty());
        }
        catch (Throwable e) {
            onComplete.accept(Optional.of(e));
            Utils.sneakyThrow(e);
        }

        return result;
    }
}

This is only a simplified sketch to illustrate the idea.这只是一个简化的草图来说明这个想法。 The real solution would also support the primitive IntStream , LongStream , and DoubleStream真正的解决方案还将支持原语IntStreamLongStreamDoubleStream

Check out these complete implementations of AutoClosingReferenceStream, AutoClosingIntStream, AutoClosingLongStream and AutoClosingDoubleStream at the open-source project Speedment https://github.com/speedment/speedment/tree/master/src/main/java/com/speedment/internal/core/stream/autoclose在开源项目 Speedment 上查看 AutoClosingReferenceStream、AutoClosingIntStream、AutoClosingLongStream 和 AutoClosingDoubleStream 的这些完整实现https://github.com/speedment/speedment/tree/master/src/main/java/com/speedment/internal/core/流/自动关闭

The solution is similar to the one mentioned by @LukasEder该解决方案类似于@LukasEder 提到的解决方案

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Akka等待流完成 - Akka wait for stream completion Java 并行流 forEach 完成 - Java parallel stream forEach completion 如何连接到802.15.4帧流? - how can I hook into a stream of 802.15.4 frames? Spring Boot-如何为非GUI应用程序注册关闭挂钩 - Spring Boot - How to register a shutdown hook for a non-GUI application Apache Camel:由于JVM状态而无法注册关闭挂钩 - Apache Camel: Unable to register shutdown hook due to JVM state 是否有任何钩子可以避免在Java中用垃圾回收注册线程? - Is there any hook provoided to register a thread with garbage collection in java? Java。 按完成顺序连接线程,并在每次连接后运行一个钩子 - Java. Join threads in order of completion and run a kind of a hook after each join Kafka Streams Shutdown Hook 和同一 Stream 应用程序中的意外异常处理 - Kafka Streams Shutdown Hook and Unexpected Exception Handling in the same Stream application 如何为 gs 注册 URL stream 处理程序:Java Z38008ECDD81C2CEAFF1D71 中的协议 - How to register a URL stream handler for the gs: protocol in Java Spring log4j2 无法注册关闭挂钩,因为 JVM 正在关闭 - log4j2 Unable to register shutdown hook because JVM is shutting down
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM