ListIterator
ListIterator extends Iterator to provide bidirectional traversal of List elements, along with the ability to add, set, and remove elements during iteration. Where Iterator can only move forward and only remove the current element, ListIterator can move both forward and backward, can query the current index (nextIndex, previousIndex), can replace the last element returned (set), and can insert a new element at the current position (add). ListIterator is specific to List — it is obtained from List.listIterator() and only applies to ordered, indexed collections. This entry covers all ListIterator methods, bidirectional traversal patterns, the state model for set and add, practical use cases, and how ListIterator enables in-place list transformation.
ListIterator Methods — Bidirectional Traversal
// ── ListIterator creation ─────────────────────────────────────────────
List<String> list = new ArrayList<>(
List.of("A", "B", "C", "D", "E"));
// Start from the beginning:
ListIterator<String> it = list.listIterator();
// Start from a specific index:
ListIterator<String> fromMiddle = list.listIterator(2); // cursor before index 2
// ── Forward traversal ─────────────────────────────────────────────────
while (it.hasNext()) {
int idx = it.nextIndex(); // index of next element
String val = it.next(); // returns element, advances cursor
System.out.printf("[%d]=%s ", idx, val);
}
// [0]=A [1]=B [2]=C [3]=D [4]=E
// ── Backward traversal ────────────────────────────────────────────────
// it is now positioned after last element — traverse backward
while (it.hasPrevious()) {
int idx = it.previousIndex(); // index of previous element
String val = it.previous(); // returns element, retreats cursor
System.out.printf("[%d]=%s ", idx, val);
}
// [4]=E [3]=D [2]=C [1]=B [0]=A
// ── Index queries ─────────────────────────────────────────────────────
ListIterator<String> pos = list.listIterator(2); // cursor before index 2
System.out.println(pos.nextIndex()); // 2 — C would be returned by next()
System.out.println(pos.previousIndex()); // 1 — B would be returned by previous()
pos.next(); // returns C, cursor moves to between C and D
System.out.println(pos.nextIndex()); // 3 — D would be returned by next()
System.out.println(pos.previousIndex()); // 2 — C would be returned by previous()
// ── next() then previous() returns same element ───────────────────────
ListIterator<String> cursor = list.listIterator(1); // before B
String forward = cursor.next(); // B — cursor now between B and C
String backward = cursor.previous(); // B — cursor now between A and B again
System.out.println(forward.equals(backward)); // true — same elementModification Methods — add, set, remove
// ── set() — replace during traversal ────────────────────────────────
List<String> list = new ArrayList<>(
List.of("hello", "world", "java", "iterator"));
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String current = it.next();
it.set(current.toUpperCase()); // replace with uppercase version
}
System.out.println(list); // [HELLO, WORLD, JAVA, ITERATOR]
// ── remove() — remove during traversal ───────────────────────────────
List<Integer> numbers = new ArrayList<>(
List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
ListIterator<Integer> remover = numbers.listIterator();
while (remover.hasNext()) {
int n = remover.next();
if (n % 2 == 0) {
remover.remove(); // remove even numbers
}
}
System.out.println(numbers); // [1, 3, 5, 7, 9]
// ── add() — insert during traversal ──────────────────────────────────
List<String> words = new ArrayList<>(
List.of("Java", "is", "cool"));
ListIterator<String> adder = words.listIterator();
while (adder.hasNext()) {
adder.next();
adder.add("*"); // insert "*" after each word
}
System.out.println(words); // [Java, *, is, *, cool, *]
// ── State machine: what is allowed when ─────────────────────────────
// State "fresh" (before first next/previous, or after add/remove):
// next() — OK
// previous() — OK
// set() — IllegalStateException
// remove() — IllegalStateException
// add() — OK (inserts before cursor, state stays "fresh")
//
// State "element returned" (after next() or previous()):
// next() — OK
// previous() — OK
// set() — OK (replaces last returned, state stays "element returned")
// remove() — OK (removes last returned, state → "fresh")
// add() — OK (inserts at cursor, state → "fresh")
// ── Demonstrating state transitions ──────────────────────────────────
List<String> demo = new ArrayList<>(List.of("A", "B", "C"));
ListIterator<String> state = demo.listIterator();
// state = "fresh":
// state.set("X"); // IllegalStateException
state.next(); // returns "A" — state = "element returned"
state.set("Alpha"); // replaces A — state still "element returned"
state.next(); // returns "B" — state = "element returned"
state.remove(); // removes B — state = "fresh"
// state.remove(); // IllegalStateException — must call next/previous first
// state.set("X"); // IllegalStateException — must call next/previous first
System.out.println(demo); // [Alpha, C]Practical Patterns — In-Place Transformation
// ── In-place transformation ───────────────────────────────────────────
public static void squareAll(List<Integer> numbers) {
ListIterator<Integer> it = numbers.listIterator();
while (it.hasNext()) {
int n = it.next();
it.set(n * n); // replace with square — O(1) per element
}
}
// Works efficiently for both ArrayList and LinkedList:
List<Integer> arrayList = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> linkedList = new LinkedList<>(List.of(1, 2, 3, 4, 5));
squareAll(arrayList);
squareAll(linkedList);
System.out.println(arrayList); // [1, 4, 9, 16, 25]
System.out.println(linkedList); // [1, 4, 9, 16, 25]
// ── Insert between every pair of elements ─────────────────────────────
public static <T> void intersperse(List<T> list, T separator) {
ListIterator<T> it = list.listIterator();
if (!it.hasNext()) return;
it.next(); // advance past first element
while (it.hasNext()) {
it.add(separator); // insert before current position
it.next(); // advance past the original next element
}
}
List<String> words = new ArrayList<>(List.of("a", "b", "c", "d"));
intersperse(words, "-");
System.out.println(words); // [a, -, b, -, c, -, d]
// ── Reverse a sublist in-place ─────────────────────────────────────────
public static <T> void reverseSubList(List<T> list, int from, int to) {
// ListIterator from both ends, swap until they meet
ListIterator<T> fwd = list.listIterator(from);
ListIterator<T> rev = list.listIterator(to);
while (fwd.nextIndex() < rev.previousIndex()) {
T front = fwd.next();
T back = rev.previous();
fwd.set(back);
rev.set(front);
}
}
List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7));
reverseSubList(nums, 2, 5); // reverse indices [2, 5)
System.out.println(nums); // [1, 2, 5, 4, 3, 6, 7]
// ── Efficient LinkedList processing — O(n) total ──────────────────────
// WRONG for LinkedList — O(n²) total:
List<String> bigLinked = new LinkedList<>(/* 10000 elements */);
for (int i = 0; i < bigLinked.size(); i++) {
String s = bigLinked.get(i); // O(n) traversal for each index
bigLinked.set(i, s.trim()); // O(n) traversal again
} // Total: O(n²) — catastrophically slow
// CORRECT for LinkedList — O(n) total:
ListIterator<String> efficient = bigLinked.listIterator();
while (efficient.hasNext()) {
String s = efficient.next(); // O(1) — follows node.next reference
efficient.set(s.trim()); // O(1) — modifies current node
} // Total: O(n) — fast