JAVA中简单的for循环竟有这么多坑,你掉进去了吗

实践的事务项目开发中,咱们应该对从给定的list中除去不满足条件的元素这个操作不生疏吧?

许多同学能够马上想出许多种完成的办法,但你想到的这些指针式万用表使用方法完成办法都是人畜无害的吗?许多看似正常的java培训操作其实背后是个陷阱,许多新手或java面试题许稍不留神就会掉入其间。

倘若不幸踩中:

  • 代码运行时直接抛反常报错,这个算是不幸中的万幸,至少能够及时发现并去处理
  • 代码运行不报错,可是事务逻辑莫名其妙的呈现各种古怪问题,这种就比较悲惨剧了,因为这个问题稍不留神的话,或许就会给后续事务埋下隐患。

那么,到底有哪些完成办法呢?哪些完成办法或许会存在问题呢?这儿咱们一同讨论下。留意哦,这儿讨论的可不是茴香豆的“茴”字有有种写法的问题,而是很严厉很现按钮英文实也很简略被疏忽的技术问题。

假设需求场景:

给定一个用户列表allUsers,需求从该列表中除去隶属部分为dev的人员,将剩下的人员信息回来

踩坑操作

foreach循环除去办法

许多新手的第一想法便是for循环逐一判别校验下然后符合条件的除去掉就行了嘛~ so easy…

1分钟就把代码写完了:


public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) {
    for (UserDetail user : allUsers) {
        // 判别部分假如归于dev,则直接除去
        if ("dev".equals(user.getDepartment())) {
            allUsers.remove(user);
        }
    }
    // 回来剩下的用户数据
    return allUsers;
}

然后决心满满的点击了履行按钮


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)

诶? what are you 弄啥嘞?咋抛反常了?

JAVA中简略的for循环竟有这么多坑,你掉进去了吗

一不留神就踩坑里了,下面就一同剖析下为啥会抛反常。

原因剖析:

JAVA的foreach语法实践处理是基于迭代器It指针万用表怎么读数eratjava培训or进行完成的。

在循环javaee开始时,会首要创建一个迭代实例,这个迭代实例的expejavaeectedModCount赋值为调集的modCount。而每逢迭代器使⽤hashNext()/next()遍历下⼀个元素之前,都会检测modCount变量与expectedModCount值是否相等,相等的话就回来遍历;不然httpwatch就抛出反常ConcurrentModificationException,终⽌遍历。

假如在循环中添加或删去元素,是直接调用调集的add()remove()办法,导致了modCount添加或减少,但这些办法不会修正迭代实例中的expectedMHTTPodCount,导致在迭代实例中expectedMjava是什么意思odCounjava模拟器tmodCount的值不相等,抛出ConcurrentModificationException反常。

JAVA中简略的for循环竟有这么多坑,你掉进去了吗

下标循环操作

java怎么读哼?已然foreach办法不行,那就用原始的下标循按钮的工作原理环的办法来搞,总不会报错了吧?仍旧很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;
}

代码趁热打铁,履行一下,看下处理后的输出:


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

果然,不报错了,成果也输出了,完美~

等等?这样真的OK了吗?

咱们的代码逻辑里边是判别假如"dev".equals(d指针万用表读数图解epartment),可是输出成果里边,为啥仍是有dhttp协议epartment=dev这种本应被除去掉的数据呢?

这儿假如是在实在事务项目中,开发阶段不报错,又没有细心去验证成果的情况下,流到生产线上,就或许造成事务逻辑的反常。

接下来看下呈现这个现象的详细原因。

原因剖析:

咱们知道,list中的元素与下标之间,其实并没有强绑定关系,仅仅只是一个方位次序的对应关系,list中元素改变之后,其每个元素对应的下标都或按钮开关怎么接许会改变,如下暗示:

JAVA中简略的for循环竟有这么多坑,你掉进去了吗
那么,从List中删去元素之后,List中被删https和http的区别元素后面的所有元素下标都发生前移,可http://www.baidu.comfor循环的指针i是始终往按钮开关怎么接后累加的,再处理下一个的时分,就或许会有部分元素被漏掉没有处理。

比方下图的暗示,i=0时,判别A元素需求删去,则直接删去;再循环时i=1,此刻因为list中元素方位前移,导致B元素变成了原来下标为0的方位,直接被漏掉了:

JAVA中简略的for循环竟有这么多坑,你掉进去了吗

所以到这java培训儿呢,也就能够知java是什么意思道为啥上面的代码履行后会呈现漏网之鱼啦~

正确办法

才智了上面2个坑操作之后,那正确妥当的操作办法应该是怎么样的呢?

迭代器办法

诶?没搞错吧?前面不是刚说指针式万用表过fore指针式万用表ach办法也是使用的迭代器,可是其实是坑操作吗?这http 302儿怎么又说迭代器模式是正确办法呢?

虽然都是基于迭代器java语言,可是使用逻辑是不一样的,看下代码:


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;
}

履行成果:


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

这次居然直接履行成功了,且成果也指针式万用表使用方法是正确的。为啥呢?

在前面foreach办法的时分,咱们提过之所以会报错的原因,是由于直接修正了原始list数据而没有同步让Iterator感知到,所以导致Iterjava编译器ator操作前校验失败抛反常了。而此处的写法中,直接调用迭代器中remove()办法,此http代理操作会在调用调集的remove()add()办法后,将expectedModCount从头赋值为modCount,所以在迭代器中添加、删去元素是能够正常运行的。,所以这样就不会出问题啦。

JAVA中简略的for循环竟有这么多坑,你掉进去了吗

Lumbda表达式

言简意赅,直接上代码:


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

Stream流操作

作为JAVA8开始参加的Stream,使得这种场景https安全问题完成起来愈加的高雅与易懂:


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

中间目标辅助办法

已然前面说了不能直接循环的时分履行移除操作,那就先搞个list目标将需求移除的元素暂存起来,最后一同除去就行啦 ~

嗯,虽然有点挫,可是不得不供认,实践情况中,许多人都在用这个办法—— 说的便是你,你是不java怎么读是也曾这么写过?


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;
}

或许:


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)

回顾

好啦,关于JAVA中循环场景中对列表操作的相关内容咱们就聊这么多了~ 你有踩过上面的坑么?你还有什么更好的办法来完成吗?欢迎一同按钮开关怎么接讨论交流~

发表评论

提供最优质的资源集合

立即查看 了解详情