GitHub 1.4K Star Java Engineers become god’s path

In alibaba Java development manual, there is such a provision:

However, the manual does not give a specific reason, so this article will analyze the thinking behind the regulation.

The foreach loop

Foreach loop (Foreach loop) is a control flow statement in computer programming languages, usually used to loop over a set of numbers or elements in a set.

The Java language introduced the foreach loop beginning with JDK 1.5.0. Foreach provides developers with great convenience in traversing groups of numbers and collections.

The foreach syntax is as follows:

For (element type t element variable x: traversal object obj){Java statement that references x; }Copy the code

The following example demonstrates the use of plain for and foreach loops:

Public static void main(String[] args) {// Use ImmutableList to initialize a List of names. List<String> userNames = ImmutableList.  "hollis", "HollisChuang", "H"); System.out.println(" Use the for loop to iterate over the List"); for (int i = 0; i < userNames.size(); i++) { System.out.println(userNames.get(i)); } system.out. println(" use foreach to iterate over List"); for (String userName : userNames) { System.out.println(userName); }}Copy the code

The output of the above code is as follows:

Use foreach to iterate through List Hollis Hollis HollisChuang H with the for loopCopy the code

As you can see, using foreach syntax to iterate over collections or arrays has the same effect as a regular for loop, but the code is much cleaner. Therefore, foreach loops are also commonly referred to as enhanced for loops.

However, as a qualified programmer, we need to know not only what an enhanced for loop is, but also how it works.

Enhancing the for loop is also a syntactic candy provided by Java. If you decomcompile the class file (using jad), you can get the following code:

Iterator iterator = userNames.iterator(); do { if(! iterator.hasNext()) break; String userName = (String)iterator.next(); if(userName.equals("Hollis")) userNames.remove(userName); } while(true); System.out.println(userNames);Copy the code

You can see that the original enhanced for loop relies on the while loop and Iterator implementation. (Remember this implementation, we’ll use it later!)

Problem reproduction

The specification says it doesn’t allow us to add/remove collection elements in a foreach loop, so let’s try and see what happens.

// Use double-brace syntax to create and initialize a List List<String> userNames = new ArrayList<String>() {{add("Hollis"); add("hollis"); add("HollisChuang"); add("H"); }}; for (int i = 0; i < userNames.size(); i++) { if (userNames.get(i).equals("Hollis")) { userNames.remove(i); } } System.out.println(userNames);Copy the code

In the above code, we first create and initialize a List of four strings, Hollis, Hollis, HollisChuang, and H, using double-brace syntax.

The List is then iterated through using a normal for loop, removing elements in the List whose contents are equal to Hollis. Then print the List, which looks like this:

[hollis, HollisChuang, H]
Copy the code

So, let’s see what happens if we use an enhanced for loop:

List<String> userNames = new ArrayList<String>() {{
    add("Hollis");
    add("hollis");
    add("HollisChuang");
    add("H");
}};

for (String userName : userNames) {
    if (userName.equals("Hollis")) {
        userNames.remove(userName);
    }
}

System.out.println(userNames);
Copy the code

The code above iterates through the element using an enhanced for loop and attempts to remove the Hollis string element. Running the above code throws the following exception:

java.util.ConcurrentModificationException
Copy the code

Similarly, the reader can try adding elements in an enhanced for loop using the add method, and the result will be the same.

This exception occurs because fail-fast, an error detection mechanism for Java collections, is triggered.

fail-fast

Next, we analysis under the enhanced for loop in the add/remove elements will be thrown when the Java. Util. ConcurrentModificationException reasons, namely explain what is the fail – fast into the system, the principle of fail – fast, etc.

Fail-fast is an error detection mechanism for Java collections. When multiple threads to set (not a fail – safe collection class) for changes in the structure of operation, is likely to fail – fast mechanism, this time will throw ConcurrentModificationException (when the method detects object of concurrent modification, This exception is thrown when such modifications are not allowed.

It is also important to note that even in a non-multithreaded environment, it is also possible to throw a change exception if a single thread violates the rule.

So how does removing elements in an enhanced for loop violate the rules?

To analyze this problem, we will first enhance the for loop syntax sugar to sugar, resulting in the following code:

Public static void main(String[] args) {// Use ImmutableList to initialize a List of names. List<String> userNames = new ArrayList<String>() {{  add("Hollis"); add("hollis"); add("HollisChuang"); add("H"); }}; Iterator iterator = userNames.iterator(); do { if(! iterator.hasNext()) break; String userName = (String)iterator.next(); if(userName.equals("Hollis")) userNames.remove(userName); } while(true); System.out.println(userNames); }Copy the code

Then run the above code, which also throws an exception. Let’s take a look at the integrity of the ConcurrentModificationException stack,

Through we can go to the exception stack, where the exception happened, in call chain ForEachDemo line 23, Iterator. The next call Iterator. CheckForComodification method, The exception is thrown by the checkForComodification method.

Actually, after debug, we can find that if you remove the code has not been carried out, the iterator. Next this line is not an error. The exception is thrown when the next method is called after remove.

Let’s look directly at the checkForComodification method code to see why the exception is thrown:

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

The code is simple, modCount! = expectedModCount, will throw ConcurrentModificationException.

So, let’s see how the Remove/Add operation room causes the modCount and expectedModCount to be different.

What does Remove/Add do

First, we need to figure out what the modCount and expectedModCount variables are.

Through the source code, we can find:

  • ModCount is a member variable in the ArrayList. It represents the number of times the collection has actually been modified.
  • ExpectedModCount is a member variable in Itr, an inner class in the ArrayList. ExpectedModCount indicates how many times the iterator expects the set to be modified. Its value is initialized when the arrayList. iterator method is called. The value will change only if the collection is operated on by an iterator.
  • Itr is an implementation of Iterator, and iterators that can be retrieved using the ArrayList. Iterator method are instances of Itr classes.

Their relationship is as follows:

class ArrayList{ private int modCount; public void add(); public void remove(); private class Itr implements Iterator<E> { int expectedModCount = modCount; } public Iterator<E> iterator() { return new Itr(); }}Copy the code

In fact, many of you can guess why the Remove /add operation causes expectedModCount and modCount to not want to wait.

Through the code, we can also find that the core logic of remove method is as follows:

As you can see, it only modifies the modCount and does nothing to the expectedModCount.

Simple sum up, the reason will throw ConcurrentModificationException, because we used in the code of the enhanced for loop, and the enhanced for loop, a collection of traverse was conducted by the iterator, But element add/remove is the collection class’s own method to use directly. This causes the iterator to iterate over an element that was deleted/added without realizing it, and to throw an exception indicating that concurrent changes may have occurred!

Correct posture

At this point, we’ve explained why you can’t add/remove collections directly from the body of the foreach loop.

However, there are many times when we need to filter a collection, such as deleting some elements. How do we do this? There are several methods to consider:

1. Use Iterator directly

In addition to using the normal for loop directly, we can also use the remove method provided by Iterator directly.

    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    Iterator iterator = userNames.iterator();

    while (iterator.hasNext()) {
        if (iterator.next().equals("Hollis")) {
            iterator.remove();
        }
    }
    System.out.println(userNames);
Copy the code

If you use the remove method provided by Iterator directly, you can modify it to the value of the expectedModCount. No more exceptions will be thrown. Its implementation code is as follows:

2. Use the filter provided in Java 8

In Java 8, collections can be converted to streams, and there is a filter operation for streams. You can test the original Stream, and the elements that pass the test are left to generate a new Stream.

List<String> userNames = new ArrayList<String>() {{ add("Hollis"); add("hollis"); add("HollisChuang"); add("H"); }}; userNames = userNames.stream().filter(userName -> ! userName.equals("Hollis")).collect(Collectors.toList()); System.out.println(userNames);Copy the code

3. Using enhanced for loops is also possible

If, we are very sure in a collection, one is about to delete the element contains only a single word, such as the operation of the Set, so it is also can use the enhanced for loop, as long as after deletion, immediately end the loop body, don’t continue traversal can again, that is to say, don’t let the code execution until the next next method.

    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    for (String userName : userNames) {
        if (userName.equals("Hollis")) {
            userNames.remove(userName);
            break;
        }
    }
    System.out.println(userNames);
Copy the code

4. Use the Fail-Safe collection class directly

In Java, in addition to the generic collection classes, there are also collection classes that employ the Fail-safe mechanism. The traversal of such a collection container does not directly access the contents of the collection, but copies the contents of the original collection and traverses the copied collection.

As the iteration is to traverse the copies of the original collection, so in the process of traversing the changes to the original collection and cannot be detected by the iterator, so will not trigger a ConcurrentModificationException.

ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>() {{ add("Hollis"); add("hollis"); add("HollisChuang"); add("H"); }}; for (String userName : userNames) { if (userName.equals("Hollis")) { userNames.remove(); }}Copy the code

Based on the copy content has the advantage of avoiding the ConcurrentModificationException, but by the same token, the iterator can not access to the contents of the modified, namely: iterators iterate over a collection of began to traverse the moment he got copies of the original collection during traversal of modifying the iterator is don’t know.

Containers under the java.util.concurrent package are security failures and can be used and modified concurrently in multiple threads.

conclusion

The enhanced for loop we use is a Syntactic sugar provided by Java, implemented by Iterator to iterate over elements.

But if, instead of Iterator, the collection is added/removed through the collection class’s own methods during traversal. The fail-fast mechanism will throw an exception to warn the user that concurrent modifications may have occurred. This is called the fail-fast mechanism.

Of course, there are many ways to solve this problem. Such as Iterator for element deletion, filter for Stream, classes for fail-safe, and so on.

GitHub 1.4K Star Java engineers become god’s path, not to learn about it?

GitHub 1.4K Star Java engineers become god’s way, really not to know?

GitHub 1.4K Star Java engineers become god’s way, really sure not to check out?