Watch Out For Recursion in Java 8’s [Primitive]Stream.iterate()
An interesting question by Tagir Valeev on Stack Overflow has recently caught my attention. To keep things short (read the question for details), while the following code works:
public static Stream<Long> longs() { return Stream.iterate(1L, i -> 1L + longs().skip(i - 1L) .findFirst() .get()); } longs().limit(5).forEach(System.out::println);
printing
1 2 3 4 5
The following, similar code won’t work:
public static LongStream longs() { return LongStream.iterate(1L, i -> 1L + longs().skip(i - 1L) .findFirst() .getAsLong()); }
Causing a StackOverflowError
.
Sure, this kind of recursive iteration is not optimal. It wasn’t prior to Java 8 and it certainly isn’t with the new APIs either. But one might think it should at least work, right? The reason why it doesn’t work is because of a subtle implementation difference between the two iterate()
methods in Java 8. While the reference type stream’s Iterator
first returns the seed
and only then proceeds with iterating by applying the iteration function on the previous value:
final Iterator<T> iterator = new Iterator<T>() { @SuppressWarnings("unchecked") T t = (T) Streams.NONE; @Override public boolean hasNext() { return true; } @Override public T next() { return t = (t == Streams.NONE) ? seed : f.apply(t); } };
This is not the case for the LongStream.iterate()
version (and other primitive streams):
final PrimitiveIterator.OfLong iterator = new PrimitiveIterator.OfLong() { long t = seed; @Override public boolean hasNext() { return true; } @Override public long nextLong() { long v = t; t = f.applyAsLong(t); return v; } };
The iteration function is already pre-fetched one value in advance. This is usually not a problem, but can lead to
- Optimisation issues when the iteration function is expensive
- Infinite recursions when the iterator is used recursively
As a workaround, it might be best to simply avoid recursion with this method in primitive type streams. Luckily, a fix in JDK 9 is already on its way (as a side effect for a feature enhancement): https://bugs.openjdk.java.net/browse/JDK-8072727
Reference: | Watch Out For Recursion in Java 8’s [Primitive]Stream.iterate() from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog. |