2

Does there exist an elegant way to register callbacks when the last element of the stream has been processed and the stream is "consumed" fully?

Especially when the stream source (like a DB cursor, an Iterator<T>, or a custom Supplier<T>) is finite and can explicitly know when there is no more data.

Example:

public Stream<Row> query(String sql){
   Connection c = dataSource.openConnection();
   Stream<Row> rows = MyDB.query(sql).stream();
   c.close();
   return rows;
}

Now, it's futile to immediately close the connection, rather it would be great to schedule connection closure when the stream is fully consumed.

I know there is onClose() API but this relies on consumers explicitly call close() on the stream.

S.D.
  • 29,290
  • 3
  • 79
  • 130

3 Answers3

4

You want to register a stream close handler:

public Stream<Row> query(String sql){
    Connection c = dataSource.openConnection();
    return MyDB.query(sql).stream().onClose(() -> c.close());
}

A caller of this method must excplicitely close the stream!

(Note: I left out exception handling here.)

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
4

Call onClose, and document that the returned stream must be closed by caller.

/**
 * The returned stream must be closed.
 */
public Stream<Row> query(String sql){
    Connection c = dataSource.openConnection();
    return MyDB.query(sql).stream().onClose(() -> {
        try {
            c.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    });
}
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Does this imply the consumer of the stream has to call `close()` on the stream explicitly? For example, say the stream is constructed with an `Iterator` as the source. So, will the `onClose` automatically be called when the source `Iterator`'s `hasNext()` return `false` ? – S.D. Jun 13 '19 at 09:10
  • @S.D. Yes, caller must call close, or use try-with-resources, same as caller would need to do on any other resource-bound stream, like those returned by [`Files.lines()`](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#lines-java.nio.file.Path-java.nio.charset.Charset-). There is no automatic "stream used up" callback, because a stream might never be used up. – Andreas Jun 13 '19 at 09:12
  • @S.D. yes, on close will be invoked after close() is called – user902383 Jun 13 '19 at 09:12
  • This would be nice... but it's not invoked on terminal operations `Stream.of(1, 2, 3).onClose(() -> System.out.println("Completed")).forEach(System.out::println);`. I wonder why it wasn't designed to do so... – ernest_k Jun 13 '19 at 09:17
  • @ernest_k You're wondering why the `onClose` handler ([documented](https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html#onClose-java.lang.Runnable-) as: *"Close handlers are run when the `close()` method is called on the stream"*) doesn't get called when you don't call `close()`? Really?!? – Andreas Jun 13 '19 at 09:20
  • @Andreas Not quite :-) - I'm wondering why they didn't make terminal operations invoke the `onClose` callback (or call `close()` implicitly). I hope there's a reason other than *they didn't think of it*. Do you know why or can you think of a reason? – ernest_k Jun 13 '19 at 09:25
  • 1
    @ernest_k Because a terminal operation may not fully consume the stream. Say you have a stream named `s`, and the following code: `s.limit(10).forEach(...)`. That only consumes the first 10 values, so you can then do something else to process the rest. Only *you* know for sure when you're done with the stream, so it's *your* responsibility to close the stream. – Andreas Jun 13 '19 at 09:28
  • 3
    @Andreas do you know any actual stream source allowing to “process the rest”? – Holger Jun 13 '19 at 09:47
  • The [documentation](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/stream/Stream.html) says, "_A stream should be operated on (invoking an intermediate or terminal stream operation) only once_". Does that not mean it is impossible to "process the rest" with the same `Stream`? Or is the use of "_should_" sufficiently weak language to allow a reusable `Stream` implementation? – Slaw Jun 13 '19 at 10:16
  • 1
    @Slaw I suppose, the language is that weak, because `Stream` is an interface. The reference implementation will throw an exception when you try to use it more than once. A different thing could be a *source* that can return a new stream, however, there is no guaranty that the stream will fetch only those elements from the source, it consumes conceptionally, especially parallel streams do prefetching. For example, `BufferedReader` explicitly states, that it is in an undefined state, once its `lines()` stream has been processed. So no “process the rest” here. I don’t know of any counter-example. – Holger Jun 14 '19 at 08:27
0

A utility that works mostly, except when the returned stream is not iterated to the end:

public <T> Stream<T> wrap(Stream<T> stream, Runnable onEnd) {
    final Object endSignal = new Object();
    return Stream.concat(stream, Stream.of(endSignal))
            .peek(i -> {
                if(i == endSignal){onEnd.run();}
            })
            .filter(i -> i != endSignal)
            .map(i -> (T) i);
}
S.D.
  • 29,290
  • 3
  • 79
  • 130