foreach principle and attention pit, Iterator
1. Principle of foreach
Let's look at a piece of code first
public static void main(String[] args) { ArrayList<Object> list = new ArrayList<>(); list.add("1"); list.add("2"); for (Object o : list) { System.out.println(o); } }
After compiling, we open the. class file and find
public static void main(String[] args) { ArrayList<Object> list = new ArrayList(); list.add("1"); list.add("2"); Iterator var2 = list.iterator(); while(var2.hasNext()) { Object o = var2.next(); System.out.println(o); } }
The original for enhanced foreach was originally implemented through iterators.
2.Foreach pit
Compared with the for loop, Foreach does not need to know the size, so it is convenient to traverse the container. However, there are problems when operating on collections. Look at a piece of code
public static void main(String[] args) { ArrayList<Object> list = new ArrayList<>(); list.add("1"); list.add("2"); for (Object o : list) { if ("1".equals(o)) { list.remove(o); } } //output for (Object o : list) { System.out.println(o); } }
After output, we found that output 2
public static void main(String[] args) { ArrayList<Object> list = new ArrayList<>(); list.add("1"); list.add("2"); for (Object o : list) { if ("2".equals(o)) { list.remove(o); } } //output for (Object o : list) { System.out.println(o); } }
We just replace "1".equals(o) with "2".equals(o) and run the discovery
Throw a ConcurrentModificationException. Why?
-
Let's start with a few concepts
-
Size: collection size (refers to the number of elements)
-
modCount: perform + 1 operation for each set change
-
expectedModCount: the expected ModCount, which is used to judge whether the set has changed
-
Take a look at the remove method of ArrayList, focusing on the + 1 operation of modCount and the - 1 operation of size
public E remove(int index) { rangeCheck(index); //modCount + 1 operation modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); //size - 1 operation elementData[--size] = null; // clear to let GC do its work return oldValue; }
-
Take a look at hashNext() to determine the comparison between the current cursor subscript and size
public boolean hasNext() { return cursor != size; }
-
Take a look at the next () method of Iterator,
public E next() { //See if the collection changes 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]; }
The key is checkforconfirmation()
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
Found modcount here= Expectedmodcount determines whether an exception is thrown.
Now let's find the problem
First add ("1"), modCount + +, and then add ("1"), modCount + +. Now modCount = 2. When calling Iterator, expectedModCount = modCount; So expectedModCount = 2.
- In the first case:
First traversal: cursor = 0, remove ("1") can be performed, modCount + +, so modCount = 3, size = 1;
The second traversal: cursor = 1. After hashNext judgment, it is found that the current cursor subscript is equal to size. The traversal is not carried out, and the traversal is over. Therefore, 2 does not traverse the deletion at all, so the following traversal output 2.
- In the second case:
First traversal: normal entry, cursor = 0;
The second traversal: cursor = 1, remove ("2") and modeCount + +, so modCount = 3 and size = 1;
The third traversal: cursor = 2, or not equal to size, perform the next () method,
This is checkforconfirmation(). It is found that modCount is preceded by + 1 operation, which is not equal to the expected expectedModCount. An exception is thrown.
So what if you want to delete it
-
You need to use the remove method of Iterator. The key is that the code expectedModCount = modCount;
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; //Set the expected value equal to modCount expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
So what about adding?
Then use listIterator() to traverse the collection.