You can’t get out of your comfort zone. You never know what’s out there

preface

I believe we must have read the Alibaba development manual, and in the Alibaba development manual clearly pointed out, do not repeat the foreach loop inside the element add and remove, if you must remove elements, then please use Iterator, if there is concurrency, then you must choose lock.

foreach

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        for (String s : list) {
            if ("22".equalsIgnoreCase(s)) {
                list.remove(s);
            }
        }
        System.out.println(JSONObject.toJSONString(list));
    }
Copy the code

Output result:

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at org.example.list.Test01.main(Test01.java:22)

Process finished with exit code 1
Copy the code

Analysis of anomalies:

final void checkForComodification(a) {
    if(modCount ! = expectedModCount)throw new ConcurrentModificationException();
}
Copy the code

Compare the two values modCount and expectedModCount, so what are these two variables?

Where modCount represents the number of collection modifications, including the modifications made when calling the add method of the collection itself and the modifications made when calling the collection iterator modification method. The expectedModCount is how many times the iterator modifies the collection.

Let’s take a look at the decompiled code as follows:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            String s = (String)var2.next();
            if ("22".equalsIgnoreCase(s)) {
                list.remove(s);
            }
        }

        System.out.println(JSONObject.toJSONString(list));
    }
Copy the code

Foreach calls iterator’s next() method once each time through the loop. The remove method called in foreach is the remove method inside the ArrayList that updates the modCount property

We can look at the remove method in the ArrayList class

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
Copy the code

See that in this method, there is a modCount++ operation, that is, modCount is always updated.

In our first iteration, 11! ModCount = expectedModCount = expectedModCount = expectedModCount = expectedModCount = expectedModCount = expectedModCount Inside hasNext(), cursor! = size and then you get an error.

The remove method only modifies the modCount and does nothing to expectedModCount.

The iterator

Why does Alibaba’s specification manual define it this way?

Why does it recommend Iterator?

Whereas using the Iterator directly modifies the expectedModCount, using foreach, the remove method only modifies the modCount and does nothing to the expectedModCount, which is not the case with Iterator.

   public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String item = iterator.next();
            if("22".equals(item)){
                iterator.remove();
            }
        }
        System.out.println(JSONObject.toJSONString(list));
    }
Copy the code

Output result:

["11"."33"."44"]

Process finished with exit code 0
Copy the code

It can be seen that the result is correct. Let’s analyze it:

Let’s take a look at the decompiled code:

    public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");
        Iterator iterator = list.iterator();

        while(iterator.hasNext()) {
            String item = (String)iterator.next();
            if ("22".equals(item)) {
                iterator.remove();
            }
        }

        System.out.println(JSONObject.toJSONString(list));
    }
Copy the code

To see the implementation of the remove() method, arrayList.class: arrayList.class

    public Iterator<E> iterator(a) {
        return new 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;

        Itr() {}

        public boolean hasNext(a) {
            returncursor ! = size; }public void remove(a) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();     / / the first step

            try {
                ArrayList.this.remove(lastRet);   // Step 2: call the remove method of list
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount; 		// Step 3: modCount is the remove method to maintain updates,
                                                    // Because the first step verifies that modCount and expectedModCount are equal
            } catch (IndexOutOfBoundsException ex) {
                throw newConcurrentModificationException(); }}final void checkForComodification(a) {
            if(modCount ! = expectedModCount)throw newConcurrentModificationException(); }}Copy the code
  1. The checkForComodification() method is called to check whether modCount and expectedModCount are the same.

  2. The remove method called in foreach is the remove method inside the ArrayList that updates the modCount property.

  3. The updated modCount is reassigned to the expectedModCount variable.

New features in Java8

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("11");
        list.add("22");
        list.add("33");
        list.add("44");

        list.removeIf("22"::equals);
        System.out.println(JSONObject.toJSONString(list));
    }
Copy the code

conclusion

The for-each loop not only iterates over collections and arrays, but also allows you to iterate over any object that implements the Iterator interface. Crucially, there is no performance loss. To make changes to an array or collection (add or remove operations), loop through iterators. So when you’re looping through all the data, choose it when you can use it.