[Java] Let’s create a peekable iterator

5 minute read

Foreword

When creating a wrapper class for an iterator, there are often difficulties in implementing hasNext. If you just wrap it, you can delegate it to an internal iterator, but if you can’t take such a strategy for convenience.

It would be nice if you could peep at the following elements… So let’s make one.

Main subject

I made a class, PeekableIterator, which works as follows.

var src = java.util.List.of(3, 1, 4);

var peekIt = new PeekableIterator<>(src);
while(peekIt.hasNext()) {
    System.out.printf("Next element is %d.\n", peekIt.peek());
    System.out.printf("I will say it again. %d.\n", peekIt.peek());
    System.out.printf("See, was it really %d?\n", peekIt.next());
    System.out.println();
}
System.out.println("End");

Execution result

The next element is 3.
I will say it again. 3
You see, it was really 3, right?

The next element is 1.
I will say it again. 1
You see, it was really 1, right?

The next element is 4.
I will say it again. 4
You see, it was really 4, right?

The end

Implementation

Method configuration

It is a simple configuration that only adds the constructor and the favorite method peek(). When hasNext() is false, peek() throws an exception NoSuch~. This is a specification of Iterator#next().

PeekableIterator.java


import java.util.Iterator;
import java.util.NoSuchElementException;

public class PeekableIterator<T> implements Iterator<T> {
    public PeekableIterator(Iterable<T> iterable) {...}
    public PeekableIterator(Iterator<T> it) {...}
    
    public T peek() throws NoSuchElementException {...}

    @Override
    public T next() throws NoSuchElementException {...}
    @Override
    public boolean hasNext() {...}
}

Field configuration

The structure is as follows. Considering the possibility that it.next() may return null, nextElem and hasNext are handled as a set.

private final Iterator<T> it; // original iterator
private T nextElem; // next element
private boolean hasNext = false; // does the next element exist?

Implementing the #### method

The constructor immediately takes a peek and holds the next element in the field.

public PeekableIterator(Iterable<T> iterable) {
    this(iterable.iterator());
}
public PeekableIterator(Iterator<T> it) {
    this.it = it;

    if(it.hasNext()) {
        nextElem = it.next();
        hasNext = true;
    }
}

The method hasNext() is, after all, just a getter. next() just returns nextElem, but it needs to peek at the next element.

@Override
public boolean hasNext() {
    return hasNext;
}

@Override
public T next() throws NoSuchElementException {
    if(!hasNext()) {
        throw new NoSuchElementException();
    }

    final T ret = nextElem;
    if(it.hasNext()) {nextElem = it.next();}
    else {hasNext = false;}

    return ret;
}

Implementation of favorite peek() is also easy. But you have to check if nextElem is invalid.

public T peek() throws NoSuchElementException {
    if(!hasNext()) {
        throw new NoSuchElementException();
    }
    return nextElem;
}
**Overall code**
```java:Peekable.java import java.util.Iterator; import java.util.NoSuchElementException; public class PeekableIterator implements Iterator { // private final Iterator it; private T nextElem; private boolean hasNext = false; public PeekableIterator(Iterable iterable) { this(iterable.iterator()); } public PeekableIterator(Iterator it) { this.it = it; if (it.hasNext()) { nextElem = it.next(); hasNext = true; } } // public T peek() throws NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException(); } return nextElem; } // @Override public boolean hasNext() { return hasNext; } @Override public T next() throws NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException(); } final T ret = nextElem; if (it.hasNext()) { nextElem = it.next(); } else { hasNext = false; } return ret; } } ``` </div></details> ## Usage problems ### Problems caused by not monopolizing iterator When I received the iterator in the constructor, I didn't copy it. Therefore, if the iterator directly manipulates the iterator, the element will appear to be skipped. ```java var it = java.util.List.of(3, 1, 4).iterator(); var peekIt = new itertools.PeekableIterator<>(it); it.next(); while(peekIt.hasNext()) { System.out.printf("Next element is %d.\n", peekIt.peek()); System.out.printf("I will say it again. %d.\n", peekIt.peek()); System.out.printf("See, was it really %d?\n", peekIt.next()); System.out.println(); } System.out.println("End"); ``` **Execution result** ```plaintext The next element is 3. I will say it again. 3 You see, it was really 3, right? The next element is 4. I will say it again. 4 You see, it was really 4, right? The end ``` But it seems impossible to make the iterator completely independent. - An iterator is, so to speak, a "state", and whether or not it can be copied as it is depends on the provider. - You can create as many iterators as you can read all the elements, but the merit of delaying element acquisition disappears. - The original iterator dies when all the elements are read. - What about an iterator that returns elements indefinitely? It would be nice if there was a mechanism that allowed the iterator to have an "unavailable" attribute... ### An exception occurs when peek()ing at the end point Although it is according to the specifications. ```java var src = java.util.List.of(3, 1); var peekIt = new PeekableIterator<>(src); while(peekIt.hasNext()) { System.out.printf("The current element is %d.\n", peekIt.next()); System.out.printf("Next element is %d. Look forward to it.\n", peekIt.peek()); System.out.println(); } System.out.println("End"); ``` **Execution result** ```plaintext The current factor is 3. The next element is 1. looking forward to. The current element is 1. Exception in thread "main" java.util.NoSuchElementExceptionat ... ``` ## Existing library When deciding the interface of this program, I referred to [more_itertools.peekable](https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.peekable) of Python. However, when I examine it again, it seems that the same class is provided in Java. Let's try using it while paying attention to how the end points are processed. ### [Apache Commons: Class PeekingIterator\](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/iterators/PeekingIterator.html) You can also get an instance with peekingIterator(), but the difference with the constructor is unknown. [^1] You can use element() and peek() properly according to the purpose. Convenient for sober. ```java var src = java.util.List.of(3, 1); var peekIt = new PeekingIterator<>(src.iterator()); while(peekIt.hasNext()) { System.out.printf("The current element is %d.\n", peekIt.next()); System.out.printf("Next element is %d. Look forward to it. (peek)\n", peekIt.peek()); System.out.printf("Next element is %d. Look forward to it. (element)\n", peekIt.element()); System.out.println(); } System.out.println("End"); ``` **Execution result** ```plaintext The current factor is 3. The next element is 1. looking forward to. (peek) The next element is 1. looking forward to. (element) The current element is 1. The next element is null. looking forward to. (peek) Exception in thread "main" java.util.NoSuchElementException at ... ``` ### [Guava: Interface PeekingIterator\](https://guava.dev/releases/19.0/api/docs/com/google/common/collect/PeekingIterator.html) Receiving an instance via [Iterators.peekingIterator](https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/Iterators.html#peekingIterator-java.util.Iterator-) become. There is no element() method here, and the terminal peek() seems to end by throwing an exception. ```java var src = java.util.List.of(3, 1); var peekIt = Iterators.peekingIterator(src.iterator()); while(peekIt.hasNext()) { System.out.printf("The current element is %d.\n", peekIt.next()); System.out.printf("Next element is %d. Look forward to it.\n", peekIt.peek()); System.out.println(); } System.out.println("End"); ``` ```plaintext The current factor is 3. The next element is 1. looking forward to. The current element is 1. Exception in thread "main" java.util.NoSuchElementException at ... ``` ## Postscript The end. [^1]: Since I haven't investigated it so seriously, it may come out immediately if I google it unexpectedly.