In Java there is your standard Iterator
that has been around “forever” (since Java 1.2) and has hasNext()
and next()
methods, to check if the iterator has more elements and to get the next one,
respectively. This is often sufficient, and can work well (though imperatively).
Java 5 added a new Iterable
interface along with the “enhanced for” loop, from which you can get an
Iterator
and a couple other things (primarily a forEach()
method).
These serve developers well enough, but interestingly Java has no standard
iterator that allows peeking ahead, without actually retrieving values from the
iterator. The Queue
interface does have a peek()
but there is no general ability to peek for other
things that can be iterated over.
While working on a project recently, I came across the need to be able to peek ahead, and decided to roll my own. I’m sure there are some open source libraries that provide a peekable iterator, but I figured it would be “fun” to do it myself (and not spend any time looking for and adding the 3rd party dependency).
A PeekableIterator<T>
Interface
I decided to keep it simple, and just address my use case, which required peeking just one element ahead. A more general implementation could allow the user to specify some (small) number of peek ahead values to keep track of but I only needed one element. So I defined this new interface as
public interface PeekableIterator<T> extends Iterator<T> {
/** An iterator with a one-element peek-ahead.
*
* Peek one value ahead, if present. If peek() is called multiplle times before
* a call to next(), the same value is always returned.
*
* @returns Optional.of(T) if there is another element, or Optional.empty()
*/
Optional<T> peek();
}
It is a pretty straightforward interface. The Java Optional
type is a good fit here, since it’s easy to then determine if there’s a
value or not and get the value at the same time if present. Java’s Optional
is
somewhat akin to Rust’s Option
type. You’ll see why I mention that in a bit.
The Optional
type also has some nice “functional”-style methods like map()
and filter()
and such-like.
Note
One thing I don’t like about the
Optional
type is itsifPresent()
method. TheifPresent()
method takes a closure, but returnsvoid
, so the action you provide as a parameter can’t directly return a value. I think this is a misfire. I think it would be better to have returnedT
. Oh well. I suppose callingmap()
could be the alternative, depending on the use case.
I implemented the interface as:
import java.util.Iterator;
import java.util.Optional;
public class IterablePeekableIterator<T> implements PeekableIterator<T> {
private final Iterator<T> iterator;
private Optional<T> peeked = Optional.empty();
private boolean hasPeeked = false;
public IterablePeekableIterator(Iterable<T> iterable) {
this.iterator = iterable.iterator();
}
@Override
public boolean hasNext() {
return hasPeeked || iterator.hasNext();
}
@Override
public T next() {
if (hasPeeked) {
hasPeeked = false;
return peeked.orElse(null);
}
return iterator.next();
}
@Override
public Optional<T> peek() {
if (!hasPeeked && iterator.hasNext()) {
peeked = Optional.ofNullable(iterator.next());
hasPeeked = true;
}
return hasPeeked ? peeked : Optional.empty();
}
}
I wanted this implementation to work for anything that was iterable, so the
constructor takes Iterable
. As I mentioned, I only needed one peek-ahead value,
so I keep track of that with a simple boolean, and save the peeked value (if any)
in an Optional
. If you call peek()
multiple times, the same value is always
returned, as the Javadoc described. On a call to next()
if there is a peek-ahead
value, it is used and the peeked status is reset.
This worked well, and met my requirements. But as I continued with this project, I realized I had another requirement. What I was doing was something like:
var t = tokens.peek().get();
if (t == X || t == Y || t == Z) {
tokens.next(); // consume token from iterator
...
}
Basically, I wanted to consume the next token if it met some criteria. And I need to do this in many different places. In another place rather than my fictional X, Y and Z values, I had A, B and C values. What I wanted was something I use in Rust on iterators, a “next if”. Or rather, Rust has this on its built-in “peekable” iterator that returns an Option. In Rust I can do something like
if let Some(t) = tokens.next_if(|t| t == foo) {
// ...
}
So what the heck, I decided to implement that as well. It turns out to be quite
easy, and I could do it as a default implementation on the PeekableIterator
interface. My new and improved PeekableIterator
is:
public interface PeekableIterator<T> extends Iterator<T>, Iterable<T> {
Optional<T> peek();
default Optional<T> nextIf(Predicate<T> p) {
if (peek().isPresent() && p.test(peek().get())) {
return Optional.of(next());
}
return Optional.<T>empty();
}
}
Now in my class using the PeekableIterator
I can do
var t = tokens.nextIf((t) -> t == FOO || t == BAR);
The result is an Optional
so I could do any tests and mappings and what not,
like map()
and such, as needed.
This worked out really well for me. It’s not as good as what Rust provides, though with the additional pattern matching support now in Java 24, it’s getting closer.
Conclusion
This ability to peek ahead at iterator values comes up a lot (particularly if you’re building parsers), so I hope this is useful to someone can you can adapt it to your own uses. If you have any suggestions for improvement, or any other feedback, please leave them in the comments!
Happy coding.