One of the Stream APIs greatest features is its laziness. The whole pipeline is constructed lazily, stored as a set of instructions, akin to a SQL execution plan. Only when we invoke a terminal operation, the pipeline is started. It is still lazy, meaning that some operations may be short circuited.
Some third party libraries produce streams that are not entirely lazy. For example, jOOQ until version 3.12 eagerly executed a SQL query when calling
While this is probably a bug in client code, not executing the statement in this case might still be a useful feature. The exception being, of course, if the query contains a
And then:
While we’re fixing this in jOOQ 3.13 (https://github.com/jOOQ/jOOQ/issues/4934), you may be stuck to an older version of jOOQ, or have another library do the same thing. Luckily, there’s an easy trick to quickly make a third party stream “lazy”. Flatmap it! Just write this instead:
The following small test illustrates that the
ResultQuery.stream()
, regardless if the Stream is consumed afterwards:
try (var stream = ctx.select(T.A, T.B).from(T).stream()) {
// Not consuming the stream here
}
FOR UPDATE
clause, in case of which the user probably uses Query.execute()
instead, if they don’t care about the result.
A more interesting example where laziness helps is the fact that we might not want this query to be executed right away, as are perhaps still on the wrong thread to execute it. Or we would like any possible exceptions to be thrown from wherever the result is consumed, i.e. where the terminal operation is called. For example:
try (var stream = ctx.select(T.A, T.B).from(T).stream()) {
consumeElsewhere(stream);
}
public void consumeElsewhere(Stream<? extends Record> stream) {
runOnSomeOtherThread(() -> {
stream.map(r -> someMapping(r))
.forEach(r -> someConsumer(r));
});
}
try (var stream = Stream.of(1).flatMap(
i -> ctx.select(T.A, T.B).from(T).stream()
)) {
consumeElsewhere(stream);
}
stream()
is now being constructed lazily
public class LazyStream {
@Test(expected = RuntimeException.class)
public void testEager() {
Stream<String> stream = stream();
}
@Test
public void testLazyNoTerminalOp() {
Stream<String> stream = Stream.of(1).flatMap(i -> stream());
}
@Test(expected = RuntimeException.class)
public void testLazyTerminalOp() {
Optional<String> result = stream().findAny();
}
public Stream<String> stream() {
String[] array = { "heavy", "array", "creation" };
// Some Resource Problem that might occur
if (true)
throw new RuntimeException();
return Stream.of(array);
}
}
Caveat
Depending on the JDK version you’re using, the above approach has its own significant problems. For example, in older versions of the JDK 8,flatMap()
itself might not be lazy at all! More recent versions of the JDK have fixed that problem, including JDK 8u222: https://bugs.openjdk.java.net/browse/JDK-8225328