Java 8 takeWhile implementation
New in Java 8 is the Streams API, which processes data in a declarative way (https://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html). The core concept is that collections store data and streams manipulate data.
The Streams API also provides the ability to easily create a stream of values using internal iteration:
//don’t forget a limit! Otherwise your stream will be infinite
Stream.iterate(0, i -> i+1).limit(10).forEach(System.out::println);
//output: 0 1 2 3 4 5 6 7 8 9
Primitive streams are included to avoid expensive boxing and unboxing operations; the syntax is also more intuitive:
IntStream.range(0, 10).forEach(System.out::println);
//output: 0 1 2 3 4 5 6 7 8 9
To get a better understanding of the Streams API lets look at a simple problem from Project Euler: By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms. We can even expand our solutions usability by passing an infinite Fibonacci sequence and value that is not to be exceeded, this way we can try out different numbers. In fact our initial solution is only a couple lines!
IntSupplier fib = new IntSupplier(){
private int previous = 0;
private int current = 1;
public int getAsInt(){
int oldPrevious = this.previous;
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return oldPrevious;
}
};
int sum = IntStream.generate(fibonacciSequence).filter(i -> i % 2 == 0).sum();
Recognize a problem? This solution will create an infinite stream because nothing tells the stream when to end. One solution is to create a predicate that would end the stream at a certain value, say when i > 4_000_000. Essentially the function needs to find the longest prefix of elements that satisfy the predicate. Both Haskell and Scala provide support for this with the takeWhile method.
In Haskell:
takeWhile(<3) [1,2,3,4,5] == [1,2].
Unfortunately the Streams API does not currently support this function, although Java 9 will support takeWhile. Java does make implementing this method easier by providing the Spliterator Interface, which stands for “split-able iterator”, and is used to traverse the elements of a source.
First the Spliterator object must be constructed, the Spliterator.OfInt interface is implemented in order to avoid boxing and unboxing primitives; it accepts a source and a condition.
public TakeWhileSpliterator(java.util.Spliterator.OfInt source, IntPredicate condition){
this.source = source;
this.condition = condition;
}
static <T> TakeWhileSpliterator<T> over(java.util.Spliterator.OfInt ofInt, IntPredicate condition2) {
return new TakeWhileSpliterator<T>(ofInt, condition2);
}
private final java.util.Spliterator.OfInt source;
private final IntPredicate condition;
//explained later, is needed to properly end the tryAdvance() method
private boolean conditionHolds = true;
The Spliterator interface defines four methods: tryAdvance(), trySplit(), estimateSize(), and characteristics(). The tryAdvance() method is of interest because it sequentially consumes the elements of the Spliterator returning true if there are still other elements to be traversed.
Incidentally the default behavior of tryAdvance() poses a problem, tryAdvance() must return false and end the stream when the predicate (i -> I < 4_000_000) is false, regardless of the elements in the Spliterator. To achieve this every element in the source is evaluated against the predicate and a Boolean is set to false when the predicate is false. This Boolean evaluated with tryAdvance will force the return of tryAdvance to false if either the predicate is false, or if there are no more elements in the stream.
public boolean tryAdvance(IntConsumer action) {
return conditionHolds && source.tryAdvance((IntConsumer)(e) -> {
if (condition.test(e)) {
action.accept(e);
}else
conditionHolds = false;
});
}
Once the Spliterator is properly constrained it must be converted to a stream for usability. StreamSupport.stream(spliterator, boolean) supports this (the Boolean is false since our stream isn’t parallel) which fits nicely into a StreamUtilities class:
public class StreamUtils {
public static <T> Stream<Integer> takeWhile(IntStream intStream, IntPredicate condition) {
return StreamSupport.stream(TakeWhileSpliterator.over(intStream.spliterator(), condition), false);
}
}
Now to call the method and complete the problem:
StreamUtils.takeWhile(IntStream.generate(fib).filter(i -> i % 2 == 0), i -> i < 4_000_000);
To see the full code check out the git repository.