The seemingly simple for loop in Java has so many pitfalls?

In actual business project development, everyone should be 从给定的list中剔除不满足条件的元素familiar with this operation, right?

Many students can immediately think of many ways to realize it, but are all the ways you think of it 人畜无害? Many seemingly normal operations are actually traps behind them , and many novices may fall into them if they are not careful.

If unfortunately stepped on:

  • When the code is running, it directly throws an exception and reports an error. This is a blessing in misfortune. At least it can be discovered and resolved in time.
  • The code runs without reporting an error, but various strange problems appear in the business logic inexplicably, which is more tragic, because if you don't pay attention to this problem, it may bury hidden dangers for the follow-up business.

So, what are the implementation methods? Which implementations may have problems? Here we discuss it together. Pay attention, what is being discussed here is not the issue of the word "fennel" in fennel beans having a certain way of writing, but a very serious and realistic technical issue that is easily overlooked.

Hypothetical requirement scenario:

Given a user list allUsers, it is necessary to remove the personnel whose department is dev from the list, and return the remaining personnel information

Step on pit operation

foreach loop elimination method

The first thought of many novices is that the for loop judges and checks one by one and then eliminates the qualified ones ~ so easy...

The code was written in 1 minute:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

复制代码

Then confidently clicked the execute button:


java.util.ConcurrentModificationException: null
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13)
	at com.veezean.demo4.Main.main(Main.java:26)

复制代码

If you don't pay attention, you step into the pit. Let's analyze why the exception is thrown.

Cause Analysis:

The actual processing of JAVA's foreach syntax is implemented based on the iterator Iterator .

At the beginning of the loop, an iterative instance will be created first, and expectedModCountthe assignment of this iterative instance is a set modCount. And whenever the iterator uses hashNext()/ next()traverses the next element, it will modCountcheck expectedModCountwhether the variable and the value are equal, and return to the traversal if they are equal; otherwise, an exception will be thrown ConcurrentModificationExceptionto terminate the traversal.

If you add or delete elements in the loop, you directly call the method of the collection add(), remove()resulting in modCountan increase or decrease, but these methods will not modify the value in the iterative instance, resulting in an unequal value expectedModCountin the iterative instance , and a ConcurrentModificationException will be thrown.expectedModCountmodCount

Subscript loop operation

uh-huh? Since the foreach method does not work, then use the original subscript loop method to do it , and it will not report an error, right? Still very easy...


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (int i = 0; i < allUsers.size(); i++) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(allUsers.get(i).getDepartment())) {
            allUsers.remove(i);
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

复制代码

The code is completed in one go, execute it, and look at the processed output:


{id=2, name='李四', department='dev'}
{id=3, name='王五', department='product'}
{id=4, name='铁柱', department='pm'}

复制代码

Sure enough, no error was reported, and the result was output, perfect~

etc? Is this really OK?

Our code logic is to judge if "dev".equals(department), but in the output result, why is there still department=devsuch data that should be eliminated?

Here, if it is in a real business project, if there is no error reported in the development stage, and the result is not carefully verified, it may flow to the production line, which may cause abnormal business logic.

Next, let's look at the specific reasons for this phenomenon.

Cause Analysis:

We know that there is actually no strong binding relationship between the elements in the list and the subscripts, but only a corresponding relationship of position order. After the elements in the list are changed, the subscripts corresponding to each element may change, as follows hint:

Then, after an element is deleted from the List, the subscripts of all elements behind the deleted element in the List will move forward, but the pointer forof the loop iis always accumulated backwards, and when the next one is processed, some elements may be deleted. Omissions are not dealt with.

For example, as shown in the figure below i=0, when it is judged that element A needs to be deleted, it is deleted directly; when recycling i=1, at this time, because the position of the element in the list moves forward, the element B becomes the original position with subscript 0, and is directly missed up:

So at this point, you can also know why the above code will slip through the net~

correct way

After seeing the above two pit operations, what should be the correct and proper way of operation?

iterator method

Eh? Are you right? Didn’t I just say that the foreach method also uses iterators, but is it actually a pit operation? How can it be said that the iterator mode is the correct way here?

Although they are all based on iterators, the usage logic is different . Take a look at the code:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    Iterator<UserDetail> iterator = allUsers.iterator();
    while (iterator.hasNext()) {
        // 判断部门如果属于dev,则直接剔除
        if ("dev".equals(iterator.next().getDepartment())) {
            // 这是重点,此处操作的是Iterator,而不是list
            iterator.remove();
        }
    }
    // 返回剩余的用户数据
    return allUsers;
}

复制代码

Results of the:


{id=3, name='王五', department='product'}
{id=4, name='铁柱', department='pm'}

复制代码

This time, the direct execution was successful, and the result was correct. why?

In the previous foreachmethod, we mentioned that the reason why the error was reported was that the original data was directly modified listwithout synchronization Iterator, so Iteratorthe pre-operation verification failed and an exception was thrown. In the writing method here, the method in the iterator is called directly , and this operation will be reassigned after remove()calling the method of the collection , so adding and deleting elements in the iterator can work normally. , so there will be no problem.remove()add()expectedModCountmodCount

Lambda expression

In a nutshell, directly on the code:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    allUsers.removeIf(user -> "dev".equals(user.getDepartment()));
    return allUsers;
}


复制代码

Stream operation

Stream, which was added as JAVA8, makes this scenario more elegant and understandable:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    return allUsers.stream()
            .filter(user -> !"dev".equals(user.getDepartment()))
            .collect(Collectors.toList());
}

复制代码

intermediate object helper

Since it was said earlier that the removal operation cannot be performed when the loop cannot be performed directly, first create a list object to temporarily store the elements that need to be removed, and then remove them together at the end~

Well, although it is a bit frustrating, I have to admit that in actual situations, many people are using this method - I am talking about you, have you ever written like this?


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> needRemoveUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if ("dev".equals(user.getDepartment())) {
            needRemoveUsers.add(user);
        }
    }
    allUsers.removeAll(needRemoveUsers);
    return allUsers;
}

复制代码

or:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    List<UserDetail> resultUsers = new ArrayList<>();
    for (UserDetail user : allUsers) {
        if (!"dev".equals(user.getDepartment())) {
            resultUsers.add(user);
        }
    }
    return resultUsers;
}

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a3fb24ebdf5e472fae518020b3125266~tplv-k3u1fbpfcp-zoom-1.image)

Guess you like

Origin blog.csdn.net/m0_48922996/article/details/125872782