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 List
s 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:
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 thatComparator
. - default long getExactSizeIfKnown(): Returns
estimateSize()
if the size is known SIZED, else returns-1
- default boolean hasCharacteristics(int characteristics): Returns
true
if functioncharacteristics()
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 returnsfalse
. - 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:
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