Java 移除List中的元素,这玩意讲究!

两值不等, 抛出错误。

所以上述出现报错 ConcurrentModificationException 的原因非常明了, 其实就是因为调用了 Itr的next()方法, 而next()方法每次执行时,会调check方法。 那么可以理解为,这是foreach语法糖+移除时的锅。

那么我们就避免这个语法糖 ,我们先来个习惯性编写的for循环方式:

List list = new ArrayList();

list.add(“C”);

list.add(“A”);

list.add(“C”);

list.add(“B”);

list.add(“F”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前” + list.toString());

int size = list.size();

for (int i = 0; i < size; i++) {

if (“C”.equals(list.get(i))){

list.remove(“C”);

}

}

System.out.println(“移除后” + list.toString());

这样的执行结果是啥, 报错了,IndexOutOfBoundsException 数组索引边界异常:

为啥会错啊,原因很简单:

ps: cv习惯了,蓝色字体里已经cv不分了,也不改了,大家意会即可。

所以这个示例报错的原由很简单,我编码问题,把size值提前固定为7了, 然后list的size是实时变化的。

那么我把size不提前获取了,放在for循环里面。这样就不会导致 i++使 i大于list的size了:

List list = new ArrayList();

list.add(“C”);

list.add(“A”);

list.add(“C”);

list.add(“B”);

list.add(“F”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前” + list.toString());

for (int i = 0; i < list.size(); i++) {

if (“C”.equals(list.get(i))) {

list.remove(“C”);

}

}

System.out.println(“移除后” + list.toString());

}

这样的运行结果是什么:

虽然没报错,但是没有移除干净,为什么?

其实还是因为 list的size在真实的变动 。每次移除,会让size的值 -1 , 而 i 是一如既往的 +1 .

而因为ArrayList是数组, 索引是连续的,每次移除,数组的索引值都会 ’重新编排‘ 一次。

看个图,我画个简单的例子给大家看看:

也就是说,其实每一次的remove变动, 因为我们的循环 i值是一直 增加的,

所以会造成,我们想象的  数组内第二个 C 元素 的索引是 2, 当i为2时会 拿出来检测,这个假想是不对的。

因为如果 第二个 C 元素前面的 元素发生了变化, 那么它自己的索引也会往前 移动。

所以为什么会出现 移除不干净的 现象    **,

其实简单说就是    最后一个C元素因为前面的元素变动移除/新增,它的 index变化了。

然后i > list.size() 的时候就会 跳出循环, 而这个倒霉蛋 C元素排在后面,index值在努力往前移,而 i 值在变大, 但是因为我们这边是执行remove操作, list的size 在变小。**

在 i值和 size值 两个 交锋相对的时候,最后一个C元素没来得及匹对, i就已经大于 list.size ,导致循环结束了。

这么说大家不知道能不能懂,因为对于初学者来说,可能没那么快可以反应过来。

没懂的兄弟,看我的文章,我决不会让你带着疑惑离开这篇文章,我再上个栗子,细说(已经理解的可以直接往下拉,跳过这段罗嗦的分析)。

上栗子:

我们的list 里面 紧紧有 三个元素    “A”  “C”  “C” , 然后其余的不变,也是循环里面移除”C“ 元素 。

List list = new ArrayList();

list.add(“A”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前” + list.toString());

for (int i = 0; i < list.size(); i++) {

if (“C”.equals(list.get(i))) {

list.remove(“C”);

}

}

System.out.println(“移除后” + list.toString());

先看一下结果,还是出现移除不干净:

分析:

1. list的样子:

2. 循环触发,第一次 i 的值为 0, 取出来的 元素是 A ,不符合要求:

3.继续循环, 此时list的size值 依然是不变,还是 3 ,而i的值因为 i++ 后变成了1 , 1 小于 3,条件符合,进入循环内,取出 list里 index为 1的元素:

4.这个 C符合要求, 被移除, 移除后,我们的 list状态变成了:

5. 此时此刻 list的 size 是 2 ,再一轮for循环 ,  i 的值 i++ 后继续变大,从1 变成了 2 ,  2不小于 2 ,所以循环结束了。

但是我们这时候list里面排在最后的那个C元素 原本index是 2,变成了index 1 ,这个家伙 都还没被 取出来, 循环结束了,它就逃过了检测。 所以没被移除干净。

PS: 很可能有些看客 心里面会想(我YY你们会这么想), 平时用的remove是利用index移除的, 跟我上面使用的 remove(Object o) 还不一样的,是不是我例子的代码使用方法问题。

然而并不然,因为这个remove调用的是哪个,其实不是重点,看图:

结果还是一样:

其实 这样的for循环写法, 跟  list的remove 到底使用的是 Object匹配移除 还是 Index移除 , 没有关系的。 移除不干净是因为 循环 i的值 跟 list的size变动 ,跳出循环造成的。

能看到这里的兄弟, 辛苦了。

那么 使用 remove 这个方法,结合循环,那就真的没办法 移除干净了吗?

行得通的例子:

while循环 :

List list = new ArrayList();

list.add(“C”);

list.add(“A”);

list.add(“C”);

list.add(“B”);

list.add(“F”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前”+list.toString());

while (list.remove(“C”));

System.out.println(“移除后”+list.toString());

}

结果,完美执行:

为什么这么些 不会报ConcurrentModificationException错,也不会报 IndexOutOfBoundsException 错 呢?

我们看看编译后的代码:

可以看到时单纯的调用list的remove方法而已,只要list里面有"C",那么移除返回的就是true,那么就会继续触发再一次的remove(“C”),所以这样下去,会把list里面的“C”都移除干净,简单看一眼源码:

所以这样使用是行得通的。

那么当然还有文章开头我给那位兄弟说的使用迭代器的方式动态删除也是行得通的:

Iterator

List list = new ArrayList();

list.add(“C”);

list.add(“A”);

list.add(“B”);

list.add(“C”);

list.add(“F”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前” + list.toString());

Iterator it = list.iterator();

while(it.hasNext()){

String x = it.next();

if(“C”.equals(x)){

it.remove();

}

}

System.out.println(“移除后” + list.toString());

执行结果:

PS:

但是这个方式要注意的是, if判断里面的顺序,

一定要注意把 已知条件值前置 :  “C”.equals ( xxx) , 否则当我们的list内包含null 元素时, null是无法调用equals方法的,会抛出空指针异常。

那么其实我们如果真的想移除list里面的某个 元素,移除干净 。

我们其实 用removeAll ,就挺合适。

removeAll

list.removeAll(Collections.singleton(“C”));

或者

list.removeAll(Arrays.asList(“C”));

List list = new ArrayList();

list.add(“C”);

list.add(“A”);

list.add(“C”);

list.add(“B”);

list.add(“F”);

list.add(“C”);

list.add(“C”);

System.out.println(“未移除前” + list.toString());

list.removeAll(Collections.singleton(“C”));

System.out.println(“移除后” + list.toString());

运行结果:

这里使用removeAll ,我想给大家提醒一点东西 !

list.removeAll(Collections.singleton(“C”));

list.removeAll(Arrays.asList(“C”));

这两种写法 运行移除 C 的时候都是没问题的。

但是当list里面有 null 元素的时候,我们就得多加注意了, 我们想移除null元素的时候 ,先回顾一下 remove这个小方法,没啥问题,使用得当即可:

意思是,remove 可以移除 空元素,也就是 null 。

但是我们看看 removeAll :

也就是说如果list里面有 null 元素, 我们又想使用removeAll, 怎么办?

首先我们使用

list.removeAll(Collections.singleton(null));

运行结果,没问题:

接着我们也试着使用

list.removeAll(Arrays.asList(null));

运行结果,报错,空指针异常:

其实是因为 Arrays.asList这个方法 , 请看源码:

再看new ArrayList的构造方法,也是不允许为空的:

PS: 但是这只是构造方法的规定,千万别搞错了 ,ArrayList是可以存储 null 元素的 。 add(null) 可没有说不允许null元素传入。

回到刚刚的话题, 那么我们运行没有问题的 Collections.singleton(null) 怎么就没报 空指针异常呢?

那是因为返回的是Set, 而 Set的构造方法也是允许传入null的 :

所以在使用removeAll的时候,想移除 null 元素, 其实只需要传入的集合里面 是null 元素 就可以,也就是说,可以笨拙地写成这样,也是ok的 (了解原理就行,不是说非得这样写,因为后面还有更好的方法介绍):

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。

以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考

一份还热乎的蚂蚁金服面经(已拿Offer)面试流程4轮技术面+1轮HR

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
图片转存中…(img-Kk0pPoEz-1712039267431)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。

以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考

[外链图片转存中…(img-qA5jvBvW-1712039267432)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值