Iterator mode
Iterator mode is a design mode that separates the addition and deletion of a set from the sequential traversal of the set.Collections are responsible for additions, deletions, and iterators are responsible for sequential traversal of the collection's internal classes.Java's Iterator is a classic implementation of the iterator pattern.The author's version of JDK is 11.0.4. The Iterator related classes and their implementations are different in different versions of jdk. Here is an example of jdk11.0.4.
Iterator
jdk defines an Iterator interface and declares hasNext, next, and remove methods, which are used to check whether to end the traversal, traverse the next element, and delete the element, respectively.
/** * An iterator over a collection. {@code Iterator} takes the place of * {@link Enumeration} in the Java Collections Framework. Iterators * differ from enumerations in two ways: * * <ul> * <li> Iterators allow the caller to remove elements from the * underlying collection during the iteration with well-defined * semantics. * <li> Method names have been improved. * </ul> * * <p>This interface is a member of the * <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework"> * Java Collections Framework</a>. * * @apiNote * An {@link Enumeration} can be converted into an {@code Iterator} by * using the {@link Enumeration#asIterator} method. * * @param <E> the type of elements returned by this iterator * * @author Josh Bloch * @see Collection * @see ListIterator * @see Iterable * @since 1.2 */ public interface Iterator<E> { /** * Returns {@code true} if the iteration has more elements. * (In other words, returns {@code true} if {@link #next} would * return an element rather than throwing an exception.) * * @return {@code true} if the iteration has more elements */ boolean hasNext(); /** * Returns the next element in the iteration. * * @return the next element in the iteration * @throws NoSuchElementException if the iteration has no more elements */ E next(); /** * Removes from the underlying collection the last element returned * by this iterator (optional operation). This method can be called * only once per call to {@link #next}. * <p> * 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, unless an overriding class has specified a * concurrent modification policy. * <p> * The behavior of an iterator is unspecified if this method is called * after a call to the {@link #forEachRemaining forEachRemaining} method. * * @implSpec * The default implementation throws an instance of * {@link UnsupportedOperationException} and performs no other action. * * @throws UnsupportedOperationException if the {@code remove} * operation is not supported by this iterator * * @throws IllegalStateException if the {@code next} method has not * yet been called, or the {@code remove} method has already * been called after the last call to the {@code next} * method */ default void remove() { throw new UnsupportedOperationException("remove"); } /** * Performs the given action for each remaining element until all elements * have been processed or the action throws an exception. Actions are * performed in the order of iteration, if that order is specified. * Exceptions thrown by the action are relayed to the caller. * <p> * The behavior of an iterator is unspecified if the action modifies the * collection in any way (even by calling the {@link #remove remove} method * or other mutator methods of {@code Iterator} subtypes), * unless an overriding class has specified a concurrent modification policy. * <p> * Subsequent behavior of an iterator is unspecified if the action throws an * exception. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code * while (hasNext()) * action.accept(next()); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */ default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }
Iterable
The Iterable interface is an interface to an iterator object, where the iterator method returns an iterator object.
public interface Iterable<T> { /** * Returns an iterator over elements of type {@code T}. * * @return an Iterator. */ Iterator<T> iterator();
The Iterable interface is implemented in the collection class's base class interface Collection.
public interface Collection<E> extends Iterable<E>
Itr
In jdk11.0.4, both the abstract base class AbstractList of the List collection and the specific ArrayList class implement the iterator internal class Itr, ListItr.
For example, AbstractList.Itr, the attribute cursor is the index of the next element, lastRet is the index of the last returned element, generally cursor-1, and expectedModCount is used to check for ConcurrentModificationExceptions (described below).As you can see, jdk's implementation of Next and remove only checkForComodification for ConcurrentModificationExceptions is more than you can easily imagine.
private class Itr implements Iterator<E> { /** * Index of element to be returned by subsequent call to next. */ int cursor = 0; /** * Index of element returned by most recent call to next or * previous. Reset to -1 if this element is deleted by a call * to remove. */ int lastRet = -1; /** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
ArrayList.Itr is only an optimized re-implementation of AbstractList.Itr.
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; // prevent creating a synthetic constructor Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override public void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); final int size = ArrayList.this.size; int i = cursor; if (i < size) { final Object[] es = elementData; if (i >= es.length) throw new ConcurrentModificationException(); for (; i < size && modCount == expectedModCount; i++) action.accept(elementAt(es, i)); // update once at end to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
ListItr
ListItr is an extension of Itr, providing functions such as adding elements, reverse traversal, and so on.However, ListItr can only be used for subclasses of AbstractList, while Itr can be used for subclasses of all Collection s.Below is an example of AbstractList.ListItr.
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { cursor = index; } public boolean hasPrevious() { return cursor != 0; } public E previous() { checkForComodification(); try { int i = cursor - 1; E previous = get(i); lastRet = cursor = i; return previous; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.set(lastRet, e); expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; AbstractList.this.add(i, e); lastRet = -1; cursor = i + 1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }
foreach Traverses Deleted ConcurrentModificationException and its Resolution
Deleting an object using the remove method of a collection object while traversing in the foreach style may raise a ConcurrentModificationException exception, such as thrown by Main.java in the following code.
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> arr = new ArrayList<>(); arr.add("1"); arr.add("2"); arr.add("12"); for (String num: arr) { System.out.println(num.length()); if (num.length() == 2) { arr.remove(num); } } } }
javac Main.java, java Main output:
1 1 2 Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996) at Main.main(Main.java:9)
Using jdk's own decompiling tool, javap, to view the implementation of foreach-style traversal (javap-v Main), you can see that foreach traversal is actually using Iterator: 42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:() Z, 51: invokeinterface #11, 1 // Interfacejava/util/Iterator.next:() Ljava/lang/Object;
public class Main minor version: 0 major version: 55 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #17 // Main super_class: #18 // java/lang/Object interfaces: 0, fields: 0, methods: 2, attributes: 1 Constant pool: #1 = Methodref #18.#29 // java/lang/Object."<init>":()V #2 = Class #30 // java/util/ArrayList #3 = Methodref #2.#29 // java/util/ArrayList."<init>":()V #4 = String #31 // 1 #5 = Methodref #2.#32 // java/util/ArrayList.add:(Ljava/lang/Object;)Z #6 = String #33 // 2 #7 = String #34 // 12 #8 = String #35 // 3 #9 = Methodref #2.#36 // java/util/ArrayList.iterator:()Ljava/util/Iterator; #10 = InterfaceMethodref #26.#37 // java/util/Iterator.hasNext:()Z #11 = InterfaceMethodref #26.#38 // java/util/Iterator.next:()Ljava/lang/Object; #12 = Class #39 // java/lang/String #13 = Fieldref #40.#41 // java/lang/System.out:Ljava/io/PrintStream; #14 = Methodref #12.#42 // java/lang/String.length:()I #15 = Methodref #43.#44 // java/io/PrintStream.println:(I)V #16 = Methodref #2.#45 // java/util/ArrayList.remove:(Ljava/lang/Object;)Z #17 = Class #46 // Main #18 = Class #47 // java/lang/Object #19 = Utf8 <init> #20 = Utf8 ()V #21 = Utf8 Code #22 = Utf8 LineNumberTable #23 = Utf8 main #24 = Utf8 ([Ljava/lang/String;)V #25 = Utf8 StackMapTable #26 = Class #48 // java/util/Iterator #27 = Utf8 SourceFile #28 = Utf8 Main.java #29 = NameAndType #19:#20 // "<init>":()V #30 = Utf8 java/util/ArrayList #31 = Utf8 1 #32 = NameAndType #49:#50 // add:(Ljava/lang/Object;)Z #33 = Utf8 2 #34 = Utf8 12 #35 = Utf8 3 #36 = NameAndType #51:#52 // iterator:()Ljava/util/Iterator; #37 = NameAndType #53:#54 // hasNext:()Z #38 = NameAndType #55:#56 // next:()Ljava/lang/Object; #39 = Utf8 java/lang/String #40 = Class #57 // java/lang/System #41 = NameAndType #58:#59 // out:Ljava/io/PrintStream; #42 = NameAndType #60:#61 // length:()I #43 = Class #62 // java/io/PrintStream #44 = NameAndType #63:#64 // println:(I)V #45 = NameAndType #65:#50 // remove:(Ljava/lang/Object;)Z #46 = Utf8 Main #47 = Utf8 java/lang/Object #48 = Utf8 java/util/Iterator #49 = Utf8 add #50 = Utf8 (Ljava/lang/Object;)Z #51 = Utf8 iterator #52 = Utf8 ()Ljava/util/Iterator; #53 = Utf8 hasNext #54 = Utf8 ()Z #55 = Utf8 next #56 = Utf8 ()Ljava/lang/Object; #57 = Utf8 java/lang/System #58 = Utf8 out #59 = Utf8 Ljava/io/PrintStream; #60 = Utf8 length #61 = Utf8 ()I #62 = Utf8 java/io/PrintStream #63 = Utf8 println #64 = Utf8 (I)V #65 = Utf8 remove { public Main(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 3: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=4, args_size=1 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String 1 11: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 14: pop 15: aload_1 16: ldc #6 // String 2 18: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 21: pop 22: aload_1 23: ldc #7 // String 12 25: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 28: pop 29: aload_1 30: ldc #8 // String 3 32: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 35: pop 36: aload_1 37: invokevirtual #9 // Method java/util/ArrayList.iterator:()Ljava/util/Iterator; 40: astore_2 41: aload_2 42: invokeinterface #10, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 47: ifeq 87 50: aload_2 51: invokeinterface #11, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 56: checkcast #12 // class java/lang/String 59: astore_3 60: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream; 63: aload_3 64: invokevirtual #14 // Method java/lang/String.length:()I 67: invokevirtual #15 // Method java/io/PrintStream.println:(I)V 70: aload_3 71: invokevirtual #14 // Method java/lang/String.length:()I 74: iconst_2 75: if_icmpne 84 78: aload_1 79: aload_3 80: invokevirtual #16 // Method java/util/ArrayList.remove:(Ljava/lang/Object;)Z 83: pop 84: goto 41 87: return LineNumberTable: line 5: 0 line 6: 8 line 7: 15 line 8: 22 line 9: 29 line 10: 36 line 11: 60 line 12: 70 line 13: 78 line 15: 84 line 16: 87 StackMapTable: number_of_entries = 3 frame_type = 253 /* append */ offset_delta = 41 locals = [ class java/util/ArrayList, class java/util/Iterator ] frame_type = 42 /* same */ frame_type = 250 /* chop */ offset_delta = 2 } SourceFile: "Main.java"
If you do not use foreach-style traversal, use Iterator traversal directly, and delete it using the Iterator's remove method, no exceptions will be raised and the program will work correctly.
import java.util.ArrayList; import java.util.Iterator; public class Main { public static void main(String[] args) { ArrayList<String> arr = new ArrayList<>(); arr.add("1"); arr.add("2"); arr.add("12"); // for (String num: arr) { // System.out.println(num.length()); // if (num.length() == 2) { // arr.remove(num); // } // } Iterator<String> iter = arr.iterator(); while (iter.hasNext()) { String num = iter.next(); System.out.println(num.length()); if (num.length() == 2) { iter.remove(); } } } }
Why does using the remove method of a collection class throw a ConcurrentModificationException exception but not the remove method of an Iterator?This requires going back to the jdk source again.
ArrayList.Itr.remove() method:
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
ArrayList.remove(Object) method:
/** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * {@code i} such that * {@code Objects.equals(o, get(i))} * (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return {@code true} if this list contained the specified element */ public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; found: { if (o == null) { for (; i < size; i++) if (es[i] == null) break found; } else { for (; i < size; i++) if (o.equals(es[i])) break found; } return false; } fastRemove(es, i); return true; } /** * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(Object[] es, int i) { modCount++; final int newSize; if ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i); es[size = newSize] = null; }
Compared to the ArrayList remove(Object) method, the Itr remove() method checks whether the expectedModCount property of the Itr class is equal to the modCount property of the external List class (checkForComodification(); and ensures that the expectedModCount is equal to the modCount after deleting the element (expectedModCount = modCount;).This resolves the ConcurrentModificationException exception problem.
- In the checkForComodification method of the Itr class, check whether the expectedModCount property of the Itr class is equal to the modCount property of the external List class to determine whether the ConcurrentModificationException exception is thrown:
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }