Oleg Mikheev
Oleg Mikheev

Reputation: 17444

Iterator.hasNext() goes nuts after remove() from collection

The code is pretty simple:

    List lst1 = new ArrayList(){{add("1");}};
    Iterator it = lst1.iterator();
    System.out.println(it.hasNext());
    it.next();
    System.out.println(it.hasNext());
    lst1.remove(0);
    System.out.println(it.hasNext());

I know that you should never remove from a collection while iterating through its iterator, please don't tell me that.

Instead, please give me a sane explanation why would a remove() operation change the value returned by hasNext() from false to true:

$ /usr/lib/jvm/sun-jdk-1.6/bin/java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) Server VM (build 20.1-b02, mixed mode)

true
false
true

hasNext() API documentation clearly states that it should return true ONLY if there are more elements in the iterator, and calling next() returns an element. The output proves that it's not the case with SUN JDK 1.6

IBM JDK doesn't do that, hasNext returns false after remove() gets called:

$ /usr/lib/jvm/ibm-jdk-bin-1.6/bin/java -version
java version "1.6.0"
Java(TM) SE Runtime Environment (build pxi3260sr9fp2-20110625_01(SR9 FP2))
IBM J9 VM (build 2.4, JRE 1.6.0 IBM J9 2.4 Linux x86-32 jvmxi3260sr9-20110624_85526 (JIT enabled, AOT enabled)

true
false
false

Is there a bug in Sun/Oracle hasNext() method? Or, is there a bug in IBM hasNext() method? Or, should I just not bother with all that?

Upvotes: 1

Views: 3592

Answers (4)

Costi Ciudatu
Costi Ciudatu

Reputation: 38265

To really answer your question (why this happens), you need to look in the java.util.AbstractList.Itr inner class, which is returned by the iterator method of AbstractList (ArrayList does not overwrite that method). The implementation of hasNext there looks like this:

public boolean hasNext() {
    return cursor != size();
}

Your remove() call changed the value returned by size() but did not modify the cursor value hold by the Itr instance, so the two values are now different.

If they would have performed a "less than" check instead of !=, the returned value would have been what you expected, but they assumed != can only mean "less than", unless you modify the collection from outside, which is "illegal"; and in this case, any subsequent call to next() would result in an exception anyway.

Upvotes: 4

LukeH
LukeH

Reputation: 269658

The documentation is clear:

The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling [the remove] method.

The important word here is unspecified. This means that once you've modified the underlying collection then the iterator can do anything! hasNext might return true; it might return false; it might throw an exception; etc, etc.

Upvotes: 3

Bohemian
Bohemian

Reputation: 425448

While iterating over a regular Collection, you must use Iterator.remove(); to remove the current element. Any attempt at modification (adding or removing) of the Collection while iterating will throw an ConcurrentModificationException.

However, there are special Collections that allow concurrent modification to the Collection while iterating, such as CopyOnWriteArrayList et al, but element-changing operations on iterators themselves (remove, set, and add) are not supported (these methods throw UnsupportedOperationException).

Upvotes: 5

stolsvik
stolsvik

Reputation: 5341

From Iterator.remove(): "The behavior of an iterator is unspecified if the underlying collection is modified while the iteration is in progress in any way other than by calling this method."

So it is unspecified, meaning that NOTHING is a bug. It could format your harddisk in this case, and still not be incorrect.

Upvotes: 2

Related Questions