实践的事务项目开发中,咱们应该对从给定的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的foreach语法实践处理是基于迭代器It指针万用表怎么读数eratjava培训or进行完成的。
在循环javaee开始时,会首要创建一个迭代实例,这个迭代实例的expejavaeectedModCount
赋值为调集的modCount
。而每逢迭代器使⽤hashNext()
/next()
遍历下⼀个元素之前,都会检测modCount
变量与expectedModCount
值是否相等,相等的话就回来遍历;不然httpwatch就抛出反常ConcurrentModificationException
,终⽌遍历。
假如在循环中添加或删去元素,是直接调用调集的add()
,remove()
办法,导致了modCount
添加或减少,但这些办法不会修正迭代实例中的expectedMHTTPodCount
,导致在迭代实例中expectedMjava是什么意思odCounjava模拟器t
与modCount
的值不相等,抛出ConcurrentModificationException反常。
下标循环操作
嗯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中元素改变之后,其每个元素对应的下标都或按钮开关怎么接许会改变,如下暗示:
那么,从List中删去元素之后,List中被删https和http的区别元素后面的所有元素下标都发生前移,可http://www.baidu.com是for
循环的指针i
是始终往按钮开关怎么接后累加的,再处理下一个的时分,就或许会有部分元素被漏掉没有处理。
比方下图的暗示,i=0
时,判别A元素需求删去,则直接删去;再循环时i=1
,此刻因为list中元素方位前移,导致B元素变成了原来下标为0的方位,直接被漏掉了:
所以到这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
,所以在迭代器中添加、删去元素是能够正常运行的。,所以这样就不会出问题啦。
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中循环场景中对列表操作的相关内容咱们就聊这么多了~ 你有踩过上面的坑么?你还有什么更好的办法来完成吗?欢迎一同按钮开关怎么接讨论交流~