Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

When we work with collections, we often need to iterate through their elements. Java provides two essential interfaces for this purpose: Iterator and ListIterator. Though they serve a similar purpose, there are crucial distinctions between the two that we must understand.

In this tutorial, we’ll explore the differences between Iterator and ListIterator in Java.

2. The Iterator Interface

The standard Collection interface extends Iterable. Further, the Iterable interface defines the iterator() method to return an Iterator instance:

public interface Iterable<T> {
    Iterator<T> iterator();
    ...
}

Therefore, Iterator is a fundamental part of the Java Collections Framework and available for all Collection implementations, such as List and Set. It allows us to access elements in a collection sequentially without knowing its underlying structure.

It offers three primary methods, hasNext(), next(), and remove():

public interface Iterator<E> {
    
    boolean hasNext();
    E next();   
    default void remove() { throw new UnsupportedOperationException("remove"); }
    ...
}

Using the hasNext() and the next() methods, we can check if there are more elements and move to those elements.

However, the remove() method removes from the collection the last element returned by the next() method. Further, as we can see, the remove() method is a default method, so it’s optional. Its implementation depends on the underlying collection.

Let’s create a unit test to cover the basic usage of Iterator‘sthree primary methods:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
Iterator<String> it = inputList.iterator();
while (it.hasNext()) {
    String e = it.next();
    if ("3".equals(e) || "5".equals(e)) {
        it.remove();
    }
}

assertEquals(Lists.newArrayList("1", "2", "4"), inputList);

It’s worth noting that we can traverse collection only in the forward direction using an Iterator.

3. The ListIterator Interface

The ListIterator is a subtype of Iterator. Therefore, all the features Iterator offers are also available in ListIterator:

public interface ListIterator<E> extends Iterator<E> {
   
    boolean hasNext();
    E next();
    void remove();

    boolean hasPrevious();
    E previous();
    int nextIndex();
    int previousIndex();
    void set(E e);
    void add(E e);
}

As its name implies, ListItrator is used explicitly with lists. Apart from the three methods from the Iterator interface (hasNext(), next(), and remove()), the ListIterator interface has a set of new methods, such as previous(), set(), add(), etc.

Next, we’ll take a closer look at these new methods and understand the difference between Iterator and ListIterator.

For simplicity, we’ll use ArrayList as an example to understand the usage of ListIterator.

3.1. Forward and Backward Iterating Elements

ListIterator allows us to traverse a list in both forward and backward directions. Before we see how to do that, let’s understand ListIterator‘s cursor positions.

Simply put, ListIterator‘s cursor doesn’t point to an element directly. Given a list with n elements, ListIterator has the following n+1 possible cursor positions (^):

Elements:            Element_0    Element_1    Element_2    Element_3  ... Element_(n-1)
Cursor positions:  ^            ^            ^            ^           ^    ...           ^

ListIterator’s previous() and next() methods return the element before and after the current cursor position. Therefore, we should note that alternating calls to next() and previous() return the same element repeatedly. Understanding this characteristic is essential in order to implement bidirectional traversing using ListIterator.

So, let’s see this behavior through an example:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
lit.next(); // 1 2 ^ 3 4 5
 
for (int i = 1; i <= 100; i++) {
    assertEquals("2", lit.previous());
    assertEquals("2", lit.next());
}

As the code above shows, after calling the next() method twice, the cursor is located between “2” and “3”. Then, we repeated the previous() and next() calls 100 times. That is to say, it performed next() -> previous() -> next() -> previous() -> … 100 times. As we noted earlier, we’ll get the same element every time we alternate to next() and previous(). In this case, it’s “2”. We verified this with two assertions up there.

So next, let’s see how to access list elements in both directions using ListIterator:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5

assertFalse(lit.hasPrevious()); // lit is at the beginning of the list
assertEquals(-1, lit.previousIndex());

// forward
assertEquals("1", lit.next()); // after next(): 1 ^ 2 3 4 5
assertEquals("2", lit.next()); // after next(): 1 2 ^ 3 4 5
assertEquals("3", lit.next()); // after next(): 1 2 3 ^ 4 5

// backward
assertTrue(lit.hasPrevious());
assertEquals(2, lit.previousIndex());
assertEquals("3", lit.previous()); // after previous(): 1 2 ^ 3 4 5

assertTrue(lit.hasPrevious());
assertEquals(1, lit.previousIndex());
assertEquals("2", lit.previous()); // after previous(): 1 ^ 2 3 4 5

assertTrue(lit.hasPrevious());
assertEquals(0, lit.previousIndex());
assertEquals("1", lit.previous()); // after previous(): ^ 1 2 3 4 5

As the example above shows, we first used next() calls forwardly accessed the first three elements in the list. Then, we got these elements in the backward direction using previous() calls.

We’ve also used previousIndex() in the code above. ListIterator’s previousIndex() returns the index of the element that would be returned by the next call to the previous() method. Similarly, nextIndex() tells the index of the element that would be returned by a call to next().

3.2. The set() Method

ListIterator offers us the set() method to set an element’s value. The Iterator interface doesn’t support this feature. However, we should note that ListIterator.set() sets the last element from the next() or previous() call:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(1); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
assertEquals("3", lit.next()); // 1 2 ^ 3 4 5

lit.set("X");
assertEquals(Lists.newArrayList("1", "2", "X", "4", "5"), inputList);

assertEquals("X", lit.previous()); // 1 2 ^ X 4 5

assertEquals("2", lit.previous()); // 1 ^ 2 X 4 5
lit.set("Y");
assertEquals(Lists.newArrayList("1", "Y", "X", "4", "5"), inputList);

As the test above shows, when we called set(), the element returned by the last next() or previous() call got replaced by the new value.

3.3. The add() Method

ListIterator allows us to add() elements at the current cursor position following this rule:

           
Element_x (New)   ^     Element_Y
           |
           ^
           |____ add(New) 

Calling add(NEW) inserts an element before the current cursor position so that a subsequent next() call won’t be affected, and a subsequent previous() returns the new element.

An example can make this clear:

List<String> inputList = Lists.newArrayList("1", "2", "3", "4", "5");
ListIterator<String> lit = inputList.listIterator(); // ^ 1 2 3 4 5
lit.next(); // 1 ^ 2 3 4 5
lit.next(); // 1 2 ^ 3 4 5
lit.next(); // 1 2 3 ^ 4 5

lit.add("X"); // 1 2 3 X ^ 4 5
assertEquals("4", lit.next()); // 1 2 3 X 4 ^ 5; next() isn't affected

lit.previous(); // 1 2 3 X ^ 4 5
lit.previous(); // 1 2 3 ^ X 4 5
lit.previous(); // 1 2 ^ 3 X 4 5
lit.add("Y");   // 1 2 Y ^ 3 X 4 5

assertEquals("Y", lit.previous()); // previous() always return the new element

assertEquals(Lists.newArrayList("1", "2", "Y", "3", "X", "4", "5"), inputList);

4. Conclusion

In this article, we’ve discussed the usage of Iterator and ListIterator. Now, let’s summarize the key differences between them:

  • Iterator is a universal interface used to traverse any collection, while ListIterator is specific to lists and provides bidirectional iteration.
  • Iterator supports only forward iteration with next(). On the other hand, ListIterator supports both forward and backward iteration with next() and previous().
  • ListIterator includes additional methods like add() and set() to insert or replace a list element, while the Iterator interface doesn’t have these features.

As always, the complete source code for the examples is available over on GitHub.

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

res – REST with Spring (eBook) (everywhere)