当前位置: 首页 > 科技观察

Java中简单的For循环竟有这么多坑,你踩过吗_0

时间:2023-03-13 12:18:33 科技观察

Java中简单的For循环有很多陷阱。你踩到了吗?你熟悉吗?很多同学可以马上想到很多实现的方法,但是你想到的都是对人畜无害的方法吗?很多看似正常的操作其实背后都有陷阱,很多新手一不小心就可能掉进去。运气不好的话:代码运行的时候直接抛出异常报错,这是祸中之福,至少能及时发现并解决。代码运行起来没有报错,但是业务逻辑莫名其妙的出现了各种奇怪的问题,比较惨烈,因为如果不注意这个问题,可能会为后续业务埋下隐患。那么,实现方法有哪些呢?哪些实现可能有问题?下面我们一起讨论一下。注意,这里讨论的不是fennelbeans中的“fennel”字有一定写法的问题,而是一个很严肃很现实又很容易被忽视的技术问题。假设需求场景:给定一个用户列表allUsers,需要从列表中移除部门为dev的人员,并返回剩余的人员信息。踩坑操作foreach循环的方法很多新手第一个想到的就是在for循环里一个一个判断检查,符合条件的删掉~soeasy...1分钟写完代码:publicListfilterAllDevDeptUsers(ListallUsers){for(UserDetailuser:allUsers){//判断部门是否属于dev,则直接移除if("dev".equals(user.getDepartment())){allUsers.remove(用户);}}//返回剩余的用户数据returnallUsers;}然后放心的点击执行按钮:java.util.ConcurrentModificationException:nullatjava.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)atjava.util。ArrayList$Itr.next(ArrayList.java:859)在com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13)在com.veezean.demo4.Main.main(Main.java:26)嗯?你是做什么的?为什么会抛出异常?一不留神,就踩坑了。下面分析一下为什么会抛出异常。原因分析:JAVA的foreach语法的实际处理是基于迭代器Iterator实现的。在循环开始时,首先会创建一个迭代实例,并将这个迭代实例的expectedModCount赋值为集合的modCount。而每当迭代器使用hashNext()/next()遍历下一个元素时,它会检查modCount变量是否等于expectedModCountvalue,如果相等则返回遍历;否则,将抛出ConcurrentModificationException以终止遍历。如果在循环中添加或删除元素,直接调用集合的add()和remove()方法,导致modCount增加或减少,但这些方法不会修改迭代实例中的expectedModCount,导致迭代实例expectedModCount不等于modCount的值,并抛出ConcurrentModificationException。下标循环操作嗯?既然foreach方法不行,那就用原来的下标循环方法来做,就不会报错了吧?还是很easy...publicListfilterAllDevDeptUsers(ListallUsers){for(inti=0;ifilterAllDevDeptUsers(ListallUsers){Iteratoriterator=allUsers.iterator();while(iterator.hasNext()){//判断部门是否属于dev,则直接移除if("dev".equals(iterator.next().getDepartment())){//这是关键point,这里操作的是Iterator,不是listiterator.remove();}}//返回剩余用户数据returnallUsers;}执行结果:{id=3,name='王五',department='product'}{id=4,name='铁列',department='pm'}这次执行成功,结果正确。为什么?在前面的foreach方法中,我们提到报错的原因是直接修改了原始列表数据,没有同步给Iterator感知,所以Iterator在操作前检查失败,抛出了异常。这里的写法是直接调用迭代器中的remove()方法。该操作会在调用集合的remove()和add()方法后,将expectedModCount重新赋值给modCount,所以在迭代器中可以正常增删元素,所以不会有问题。Lumbda表达式简洁明了,直接上代码:returnallUsers;}Stream操作是在JAVA8开始时添加的一个Stream,让这个场景更加优雅易懂:publicListfilterAllDevDeptUsers(ListallUsers){returnallUsers.stream().filter(user->!"dev".equals(user.getDepartment())).collect(Collectors.toList());}中间对象辅助方法既然说循环的时候不能直接进行移除操作,那么先创建一个列表对象,将需要移动的元素暂存起来,最后一起移除~好吧,虽然有点折腾,但不得不承认,在实际情况下,很多人都在使用这个方法:publicListfilterAllDevDeptUsers(ListallUsers){ListneedRemoveUsers=newArrayList<>();for(UserDetailuser:allUsers){if("dev".equals(user.getDepartment())){needRemoveUsers.add(user);}}allUsers.removeAll(needRemoveUsers);returnallUsers;}或者:publicListfilterAllDevDeptUsers(ListallUsers){ListresultUsers=newArrayList<>();for(UserDetailuser:allUsers){if(!"dev".equals(user.getDepartment())){resultUsers.add(user);}}returnresultUsers;}![](https://veezean-pics-1301558317.cos.ap-nanjing.myqcloud.com/pics/202207050811299.gif)好好复习一下,关于JAVA中的循环场景我们讲了这么多关于列表操作~上面的坑你踩过吗?你有更好的方法吗?