Java Iterator, ListIterator and Spliterator

Iterator

The Java Iterator interface is available since Java 1.2. Iterator maintains a state of where we are in the current iteration, and how to get to next element. To work with Iterator, we will use these two methods:

  • boolean hasNext(): check if there is another element to iterate
  • E next(): returns the next element to iterate — throw an exception if there are no more.

There are two more methods, that seldom in use (and maybe you should not use it):

  • default void forEachRemaining(Consumer<? super E> action): Performs the given action for each remaining element until all elements have been processed or the action throws an exception.
  • default void remove(): remove the last element iterated by this iterator. The default implementation throws an instance of UnsupportedOperationException and that's all. Don't use this method unless you know what you're doing.

Obtain and Iterating in an Iterator

Let's check our first example:

List<Integer> list = new ArrayList<>(); list.add(10); list.add(20); list.add(30); Iterator it1 = list.iterator(); while(it1.hasNext()) { System.out.println(it1.next()); } Set<String> set = new LinkedHashSet<>(); set.add("apple"); set.add("beet"); set.add("carrot"); for (Iterator it2 = set.iterator(); it2.hasNext();) { System.out.println(it2.next()); }

To obtain an Iterator, we use iterator() method from a given Collection. In above example, we iterate the elements using a while loop or for loop. Both ways are valid. We can see how the hasNext() is used to check if there are more elements in the Iterator. If true, then we use next() method to obtain those element. The result is:

10
20
30
apple
beet
carrot

If an Iterator already reach the end element, function hasNext() will return false

Collection<Integer> listInt = new ArrayList<>(); listInt.add(101); listInt.add(102); listInt.add(103); System.out.println("ArrayList: " + listInt); Iterator<Integer> iterator = listInt.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } while (iterator.hasNext()) { System.out.println("Something wrong happen, since iterator.hasNext() should be false"); }

You can't re-loop this Iterator, and Iterator has no function to reset back to first element. To start again from beginning, we need to get a new Iterator using function iterator() again.

System.out.println("Let's print again..."); iterator = listInt.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); }

Result:

Let's print again...
101
102
103

forEachRemaining(...)

Since Java 8, we can use forEachRemaining(...) to iterate over all of the remaining elements in current Iterator (or an Exception happen). The parameter expected is a Consumer, a functional interface. So we can use lambda expression as below example:

System.out.println("Current list: " + listInt); iterator = listInt.iterator(); if (iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println("for each remaining:"); iterator.forEachRemaining(i -> { System.out.println(i); });

Result:

Current list: [101, 102, 103]
101
for each remaining:
102
103

forEachRemaining(...) printed 102 and 103, since 101 already printed using next().

Order of Iteration

When iterating, the order of the elements iterated in the Iterator are depends on the order of Collection type. As example, here the iteration result of LinkedList and TreeSet:

List<Integer> linkedList = new LinkedList<>(); linkedList.add(10); linkedList.add(30); linkedList.add(20); linkedList.add(50); linkedList.add(40); System.out.println("LinkedList: " + linkedList); Iterator iter1 = linkedList.iterator(); while(iter1.hasNext()) { System.out.println(iter1.next()); } Set<Integer> treeSet = new TreeSet<>(); treeSet.add(10); treeSet.add(30); treeSet.add(20); treeSet.add(50); treeSet.add(40); System.out.println("TreeSet: " + treeSet); Iterator iter2 = treeSet.iterator(); while(iter2.hasNext()) { System.out.println(iter2.next()); }

The result is:

LinkedList: [10, 30, 20, 50, 40]
10
30
20
50
40
TreeSet: [10, 20, 30, 40, 50]
10
20
30
40
50

You can see the differences. Although the order of add is same, LinkedList maintains insertion order, but TreeSet maintains ascending order.

Adding and Removal During Iteration

Let's check following example:

Collection<SimpleVO> list = new ArrayList<>(); list.add(new SimpleVO(10, "10", "Number 10")); list.add(new SimpleVO(20, "20", "Number 20")); list.add(new SimpleVO(30, "30", "Number 30")); System.out.println("ArrayList: " + list); Iterator<SimpleVO> iterator = list.iterator(); while (iterator.hasNext()) { SimpleVO vo = iterator.next(); vo.setId(vo.getId() + 5); } System.out.println("ArrayList: " + list); iterator = list.iterator(); try { while(iterator.hasNext()) { SimpleVO vo = iterator.next(); list.add(new SimpleVO(vo.getId() + 100, "100", "Number 100")); } } catch (ConcurrentModificationException cme) { System.out.println("ConcurrentModificationException occured when adding"); } System.out.println("ArrayList: " + list); iterator = list.iterator(); try { while(iterator.hasNext()) { SimpleVO vo = iterator.next(); list.remove(vo); } } catch (ConcurrentModificationException cme) { System.out.println("ConcurrentModificationException occured when remove"); } System.out.println("ArrayList: " + list); try { iterator.forEachRemaining(vo -> { System.out.println(vo); }); } catch (ConcurrentModificationException cme) { System.out.println("ConcurrentModificationException occured when call forEachRemaining(...)"); } System.out.println("ArrayList: " + list);

When iterating a Collection via an Iterator, we cannot add more element or remove an element from a Collection. ConcurrentModificationException will occurred in the subsequent call of Iterator's next() or forEachRemaining(...), as shown in the result:

ArrayList: [SimpleVO(id=10, code=10, description=Number 10), SimpleVO(id=20, code=20, description=Number 20), SimpleVO(id=30, code=30, description=Number 30)]
ArrayList: [SimpleVO(id=15, code=10, description=Number 10), SimpleVO(id=25, code=20, description=Number 20), SimpleVO(id=35, code=30, description=Number 30)]
ConcurrentModificationException occured when adding
ArrayList: [SimpleVO(id=15, code=10, description=Number 10), SimpleVO(id=25, code=20, description=Number 20), SimpleVO(id=35, code=30, description=Number 30), SimpleVO(id=115, code=100, description=Number 100)]
ConcurrentModificationException occured when remove
ArrayList: [SimpleVO(id=25, code=20, description=Number 20), SimpleVO(id=35, code=30, description=Number 30), SimpleVO(id=115, code=100, description=Number 100)]
ConcurrentModificationException occured when call forEachRemaining(...)
ArrayList: [SimpleVO(id=25, code=20, description=Number 20), SimpleVO(id=35, code=30, description=Number 30), SimpleVO(id=115, code=100, description=Number 100)]

From the sample above, we can see that although we cannot modify the Collection, but we still able to modify content of the element of the Collection. Also the adding and removing element to/from Collection actually indeed affecting the Collection, only the Iterator now become unusable.

But, how to remove an element from Collection using Iterator? Simple, don't remove directly from Collection, but use Iterator.remove(). It will remove the element that returned by previous next():

iterator = list.iterator(); while(iterator.hasNext()) { System.out.println("Remove: " + iterator.next()); iterator.remove(); } System.out.println("ArrayList: " + list);

With result:

Remove: SimpleVO(id=25, code=20, description=Number 20)
Remove: SimpleVO(id=35, code=30, description=Number 30)
Remove: SimpleVO(id=115, code=100, description=Number 100)
ArrayList: []

ListIterator

ListIterator extends the Iterator interface. ListIterator can iterate bidirectionally, you can iterate forward or backward. The cursor is always placed between 2 elements in a List, and to access element we can use next() method like Iterator, but ListIterator also equipped with previous() method to access element before the cursor. Here some of the most used methods in ListIterator:

  • void add(E e): Inserts an element into the List.
  • boolean hasNext(): Returns true when iterating in forward direction and haven't reached the 'last' element of a List.
  • boolean hasPrevious(): Returns true when iterating in backward direction and haven't reached the 'first' element of a List.
  • E next(): Returns the next element in the List.
  • int nextIndex(): Returns the index of the element that will be returned by next() function.
  • E previous(): Returns the previous element in the List.
  • int previousIndex(): Returns the index of the element that will be returned by previous() function..
  • void remove(): Removes the last element returned by next() or previous() from the List.
  • void set(E e): Replaces the last element returned by next() or previous() in the List.

From the name, ListIterator can only be applied to Lists implementation (ArrayList, LinkedList, etc.), so it can be more specific in methods. On another hand Iterator can be applied to any Collection.

Next, let's check our ListIterator example:

ListIteratorExample.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

public class ListIteratorExample {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Alpha");
        list.add("Beta");
        list.add("Gamma");
        System.out.println("Original List: " + list);         
        
        ListIterator<String> lIterator = list.listIterator();

        while (lIterator.hasNext()) {
            String next = lIterator.next();
            System.out.println(lIterator.nextIndex() + ": " + next);
            lIterator.set(next + "X");
        }

        System.out.println("Prev Index: " + lIterator.previousIndex() + ", Next Index: " +  + lIterator.nextIndex());
        lIterator.add("Delta");
        System.out.println("Prev Index: " + lIterator.previousIndex() + ", Next Index: " +  + lIterator.nextIndex());
        
        while (lIterator.hasPrevious()) {
            System.out.println(lIterator.previousIndex() + ": " + lIterator.previous());
            lIterator.remove();
        }
        System.out.println("Final List: " + list);
    }
}
                    

The result of above program is:

Original List: [Alpha, Beta, Gamma]
1: Alpha
2: Beta
3: Gamma
Prev Index: 2, Next Index: 3
Prev Index: 3, Next Index: 4
3: Delta
2: GammaX
1: BetaX
0: AlphaX
Final List: []

Spliterator

A number of new language features were introduced during the release of Java 8, included lambda functions, streams and completable futures. Inline with these new features, the Spliterator interface added to package java.util, and the Collection interface also updated with a new spliterator() method which will return a Spliterator. Spliterator is an internal iterator that can work with both Collection and Stream API. It breaks the collection or stream into smaller parts which can be processed in parallel.

Here the list of methods we can use when working with the Spliterator:

  • int characteristics(): Returns a set of characteristics of this Spliterator as an int value.
  • long estimateSize(): Returns an estimate of the number of elements that would be encountered by forEachRemaining(...) function, or else returns Long.MAX_VALUE.
  • default void forEachRemaining(Consumer<? super T> action): Performs the given action for each remaining element in the collection sequentially, until all processed or an exception thrown.
  • default Comparator<? super T> getComparator(): If this Spliterator's source is sorted by a Comparator, returns that Comparator.
  • default long getExactSizeIfKnown(): Returns estimateSize() if the size is known SIZED, else returns -1
  • default boolean hasCharacteristics(int characteristics): Returns true if function characteristics() contain all of the given characteristics.
  • boolean tryAdvance(Consumer<? super T> action): If there is remaining elements, performs the given action on it, then return true; else returns false.
  • Spliterator<T> trySplit(): If this spliterator can be partitioned, returns a Spliterator that having elements which not be covered by this Spliterator after this function.

Without further ado, here an example on how to working with Spliterator:

SpliteratorExample.java
import java.util.Collection;
import java.util.Spliterator;
import java.util.Stack;

public class SpliteratorExample {

    public static void main(String[] args) {
        Collection coll = new Stack();

        coll.add("China");
        coll.add("Japan");
        coll.add("Korea");
        coll.add("Mongolia");
        coll.add("Vietnam");
        coll.add("Laos");
        coll.add("Cambodia");
        
        // Getting Spliterator object on collection.
        Spliterator<String> splitList = coll.spliterator();
        
        // Checking sizes:
        System.out.println("Estimate size: " + splitList.estimateSize());
        System.out.println("Exact size: " + splitList.getExactSizeIfKnown());

        System.out.println("\nContent of List:");
        // using forEachRemaining() method
        splitList.forEachRemaining((n) -> System.out.println(n));

        // Obtaining another Stream to the mutant List.
        Spliterator<String> splitList1 = coll.spliterator();
        System.out.println("\nSplitList1 estimate size: " + splitList1.estimateSize());

        // Splitting it using trySplit() method
        Spliterator<String> splitList2 = splitList1.trySplit();
        System.out.println("\nAfter split >>>");
        System.out.println("SplitList1 estimate size (now): " + splitList1.estimateSize());

        // Use splitList2 first.
        if (splitList2 != null) {
            System.out.println("SplitList2 estimate size: " + splitList2.estimateSize());
            System.out.println("\nOutput from splitList2:");
            splitList2.forEachRemaining((n) -> System.out.println(n));
        }

        // Now, use the splitList1
        System.out.println("\nOutput from splitList1:");
        splitList1.forEachRemaining((n) -> System.out.println(n));
    }
}
                    

Result:

Estimate size: 7
Exact size: 7

Content of List:
China
Japan
Korea
Mongolia
Vietnam
Laos
Cambodia

SplitList1 estimate size: 7

After split >>>
SplitList1 estimate size (now): 4
SplitList2 estimate size: 3

Output from splitList2:
China
Japan
Korea

Output from splitList1:
Mongolia
Vietnam
Laos
Cambodia