Java ListIterator交替调用next()与previous()重复元素原因探究
next() and previous() on LinkedList's ListIterator Returns Duplicate Elements Great question! This behavior trips up a lot of developers at first, but once you understand how ListIterator's cursor works under the hood, it all clicks into place. Let's break this down into three key parts: the root cause, the underlying implementation logic, and the intentional design choices behind it.
1. The Root Cause: Cursor Lives Between Elements
The core misunderstanding here is where the ListIterator's cursor is positioned. Unlike some iterators that point directly at elements, ListIterator's cursor sits between elements in the LinkedList. Here's a quick visual to illustrate:
Initial state: [ ] "1" [ ] "2" [ ] "3" [ ] "4" [ ] "5" [ ] ^ Cursor starts here
- When you call
next(): The iterator returns the element immediately after the cursor, then shifts the cursor to the right of that element. After the firstnext(), you get "1", and the cursor lands between "1" and "2". - When you call
previous()right after: The iterator returns the element immediately before the cursor (which is "1" again), then moves the cursor back to the left of "1".
This back-and-forth bounce across the same element boundary is why alternating calls keep returning the same duplicate element.
2. Underlying Implementation in LinkedList's ListIterator
LinkedList's ListIterator is an internal class called ListItr, which tracks three critical variables to manage traversal:
next: The node that will be returned by the nextnext()calllastReturned: The node most recently returned bynext()orprevious()(used forremove()/set()operations)nextIndex: The index of thenextnode
Let's walk through your test case step by step to see the mechanics:
- Initialization:
nextpoints to the first node ("1"),nextIndex = 0,lastReturned = null. - First
next()call:lastReturnedis set to the "1" nodenextupdates to the "2" node (next.next)nextIndexincrements to 1- Returns "1"
- Immediate
previous()call:nextis set back to the "1" node (next.prev)lastReturnedis updated to this newnextvaluenextIndexdecrements to 0- Returns "1"
Now we’re right back to the initial state—so calling next() again will return "1" once more. That’s the loop causing duplicates.
3. Design Purpose of This Behavior
This cursor-between-elements pattern isn’t a bug—it’s intentional, and serves three key goals:
- Consistency with the Iterator Interface: The base
Iteratorinterface uses the same "between elements" cursor model, so ListIterator extends this pattern to keep behavior familiar for developers. - Safe Support for Modifications: Methods like
remove()andset()rely onlastReturnedto know which element to modify. If the cursor pointed directly at an element, these operations would be ambiguous (e.g., shouldremove()delete the element the cursor is on, or the last one returned?). - Clear Bidirectional Checks: Placing the cursor between elements makes
hasNext()andhasPrevious()straightforward:hasNext()simply checks ifnextis notnullhasPrevious()checks ifnextis not the first node (ornextIndex > 0)
Your Marker Bit Fix: A Solid Solution
Your idea of using a marker bit to track the last operation (next vs previous) is a great way to work around this behavior when you need to avoid duplicates. For example, you could add a boolean like lastWasNext—if the last call was next(), you might adjust how you handle subsequent previous() calls to skip the repeat. Alternatively, you can explicitly reset the iterator’s position with listIterator(index) if you need to jump to a specific point in the list.
Your Test Code for Reference
public class IterateLinkedListUsingListIterator { public static void main(String[] args) throws NumberFormatException, IOException { LinkedList lList = new LinkedList(); lList.add("1"); lList.add("2"); lList.add("3"); lList.add("4"); lList.add("5"); ListIterator itr = lList.listIterator(); boolean ch = true; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (ch) { System.out.println("Enter choice"); int chi = Integer.parseInt(br.readLine()); switch (chi) { case 1: if (itr.hasNext()) { System.out.println(itr.next()); } break; case 2: if (itr.hasPrevious()) { System.out.println(itr.previous()); } break; default: ch = false; } } } }
内容的提问来源于stack exchange,提问作者Rishal

