Foreach traversal principle and attention pit, Iterator

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.

  1. 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.

  1. 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.

Tags: Java set

Posted on Thu, 18 Nov 2021 01:12:12 -0500 by ratcateme