foreach 循环里禁止进行元素的 remove/add 操作

博客指出阿里巴巴禁止在Java的foreach循环里进行元素的remove/add操作,否则会报ConcurrentModificationException异常。介绍了测试demo及报错信息,解释报错原因是fail - fast机制。最后给出避免触发该机制的方法,并发用concurrent包容器,单线程Java8前用Iterator,Java8及以后可用Stream及filter。

foreach 循环里禁止进行元素的 remove/add 操作

参考网址:

https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650123395&idx=1&sn=b089eac4f561f58ee8a92602db17f577&chksm=f36bb1a2c41c38b4f36c1a84f205e188a6a8aa2684f316e4dcb8d1162b6e94b970c670b2e5b8&mpshare=1&scene=23&srcid=1117O5qEI4yxgwsIe32hRV1W&sharer_sharetime=1605575661903&sharer_shareid=9d1e76e919cc0b2f3ca23ed1f5ef67a8#rd

1.说明

  • 阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作

  • java.util.ConcurrentModificationException

foreach 循环里进行元素的 remove/add 操作会报异常ConcurrentModificationException

2.测试demo

 @Test
    public void test1(){
        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);
    }

报错信息:

java.util.ConcurrentModificationException
at java.util.ArrayListItr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.checkForComodification(ArrayList.java:901) at java.util.ArrayListItr.checkForComodification(ArrayList.java:901)atjava.util.ArrayListItr.next(ArrayList.java:851)
at com.shaoming.foreach_throwexception.ForEachTestRemove.test1(ForEachTestRemove.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunne

说明:

使用增强for循环遍历元素,并尝试删除其中的Hollis字符串元素。运行以上代码,会抛出以下异常:

java.util.ConcurrentModificationException

3.解释报错的原因

fail-fast,即快速失败,它是Java集合的一种错误检测机制。当多个线程对集合(非fail-safe的集合类)进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException(当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常)。

说的啥,我具体也是没搞懂,以后再看吧

4.正确的demo格式

package com.shaoming.foreach_throwexception;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;

/**
 * @Auther: shaoming
 * @Date: 2020/11/17 10:54
 * @Description: 为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作
 */
public class ForEachTestRemove {
    /**
     * 错误的示例代码
     * 报错:
     * java.util.ConcurrentModificationException
     */
    @Test
    public void test1() {
        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);
    }

    /**
     * 测试遍历的时候对集合元素进行增删改的操作===>使用ForEach的方式进行操作
     * 举例:
     * 拿test1这个方法机型举例
     * 我们遍历的时候,在每个string类型的元素后面拼接一个HelloWorld
     */
    @Test
    public void test2() {
        List<String> userNames = new ArrayList<String>() {{
            add("Hollis");
            add("hollis");
            add("HollisChuang");
            add("H");
        }};
        List<String> userNamesAdd = new ArrayList<String>(userNames.size());

//        userNames.clear();
        for (String userName : userNames) {
            userName = userName + "Hello World!";
            System.out.println(userName);
            userNamesAdd.add(userName);
        }
        System.out.println("开始遍历新的集合====================");
        userNamesAdd.forEach(System.out::println);
    }

    /**
     * 5种正确姿势
     */
    //1.直接使用普通的for循环进行操作
    @Test
    public void test51() {
        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")){
                //list集合remove参数i是集合的下标(索引)值
                userNames.remove(i);
            }
        }
        System.out.println(userNames);
    }
    //2.直接使用Iteraor进行操作
    @Test
    public void test52(){
        List<String> userNames = new ArrayList<String>() {{
            add("Hollis");
            add("hollis");
            add("HollisChuang");
            add("H");
        }};
        Iterator<String> iterator = userNames.iterator();
        while(iterator.hasNext()){
            String userName = iterator.next();
            if(userName.equals("Hollis")){
                iterator.remove();
            }
        }
        System.out.println(userNames);
    }
    //3.使用java8种提供的filter进行过滤
    @Test
    public void test53(){
        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);
    }
    //4.使用fail-safe的集合类进行遍历
    @Test
    public  void  test54(){
        ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>() {{
            add("Hollis");
            add("hollis");
            add("HollisChuang");
            add("H");
        }};

        for (String userName : userNames) {
            if (userName.equals("Hollis")) {
                userNames.remove();
            }
        }
    }
    //5.使用增强for循环其实也可以,不过步骤麻烦
    @Test
    public  void  test55(){
        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); 
    }

}

5.结论

以上这五种方式都可以避免触发fail-fast机制,避免抛出异常。如果是并发场景,建议使用concurrent包中的容器,如果是单线程场景,Java8之前的代码中,建议使用Iterator进行元素删除,Java8及更新的版本中,可以考虑使用Stream及filter。

Java8及更新的版本中,可以考虑使用Stream及filter。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值