鑫百利
前阵子,又名java入门者际遇了list使用remove的问题,其时我暂且给他说了一种贬责有经营。
过后,我细想,
是不是许多入门者都会碰到这种问题?
固然阿里设置手册里面有说到这个坑,可是是不是每个人都明晰?
这个造作的出现原由是什么?
奈何幸免?奈何贬责?
只可使用迭代器iterator步地吗?
removeAll?stream?removeIf?
这篇著作里,上头的万般疑问,都会触及,但不限于。
因为我经常写着写着就扯远了,可能会说到一些其他东西。
正文
随着我的思绪走,耐性读完,莫得收成你径直打我。
有个list:
Listlist=newArrayList;list.add("C");list.add("A");list.add("B");list.add("C");list.add("F");list.add("C");list.add("C");
[C,A,B,C,F,C,C]
奈何移震恐list里面的某个元素呢?
list里面给咱们提供了4个智商:
先看remove(Objecto):
这个方面字面真义看,便是,你想移除list里面的哪个Object,你传进来就不错。
看源码,如下图:
也便是说并不是想移除哪个传哪个就能移除完,而只是是只移除首个适合国法的元素。
结合例子:
当今这个List里面,存在4个"C"元素,使用remove("C"):
Listlist=newArrayList;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.remove("C");System.out.println("移除后"+list.toString);
遵循:
未移除前[C,A,C,B,F,C,C]
移除后[A,C,B,F,C,C]
是以,光这样使用remove是不行的,不成扫尾咱们需求:移除list中的通盘适合条款的元素,只是移除了适合条款的第一个元素了。
这时分,人人可能就会想,那么咱们就轮回+remove呗,这样就能把每个适合条款的移除了。
真的吗?
接着看。
轮回+remove(Objecto)/remove(Indexi):
没错,咱们不错勾通轮回,把list里面的“C”元素都移除。
轮回天然有分while轮回和for轮回(包含foreach)。
先看foreach步地:
不得行!切记!
for(Stringstr:list){if("C".equals(str)){list.remove(str);}}
代码看似没问题,可是在foreach使用list的remove/add智商都是不行的!
报错:
ConcurrentModificationException:并发极端
PS:其实若是人人曾阅读过阿里的设置轨范,也许会有极少印象。
7.[强制]不要在foreach轮回里进行元素的remove/add操作。remove元素请使用Iterator步地,若是并发操作,需要对Iterator对象加锁。
那么先无论,若是你阅读过,可能也不一定清亮里面的旨趣,是以不时往下看吧。
在分析这个造作前,我来提一嘴,一部分的ArrayList的特点:
ArrayList是基于数组扫尾的,是一个动态数组,其容量能自动增长。
ArrayList不是线程安全的。
撑持快速立时看望,通过下标序号index进行快速看望。
接下来,随着我,通盘来分析这个报错的出现(天然我的挖错步地不一定适合人人,可是也不错参考):
1.分析出错的代码段
for(Stringstr:list){if("C".equals(str)){list.remove(str);}}
光这样看,咱们只可清亮,用了foreach的语法糖,那么咱们看编译后的:
再看咱们的报错信息:
源码分析:
通过咱们反编译的代码,结合ArrayList的源码,咱们不错清亮,
Itr便是ArrayList里面的里面类,
而foreach的语法糖其实便是帮咱们new了一下Itr,先调用hashNext
while(var2.hasNext)
昭着是行为轮回的条款,那么咱们也通盘来浮浅看下这个智商源码:
1.publicbooleanhasNext{2.returncursor!=size;3.}
size是啥?
那cursor是啥?
是以,hashNext()真义是,当cursor不等于size的时分,代表还有下一位,不时轮回就完事了,这个值其实不是本次报错的要点。
咱们不时看Itr的next智商中的checkForComodification智商,便是这玩意导致报错的。
那么咱们径直定位到checkForComodification智商的源码:
代码浮浅,也看到了咱们刚才报的错ConcurrentModificationException在里面躺着。
只有modCount不等于expectedModCount,就抛错。
那么咱们就得剖释modCount和expectedModCount是啥?expectedModCount浮浅,是Itr里的一个属性,在运转换的时分,就也曾把modCount的值等赋给了expectedModCount。
其实expectedModCount便是用来纪录一脱手迭代的list的变更数modCount,至于list的变更数modCount是啥,咱们接着看。
点进去看modCount的源码:
不错看到作家果然匪面命之,这样一个字段属性,人家写了这样多凝视,那笃定是证实得尽头缜密了。
那么我来抽出一些中枢的翻译一下,给诸君望望:
此列表在结构上被修改的次数。结构修改是指改变结构尺寸的修改。
若是此字段的值随机改换,则迭代器(或列表迭代器)将在
对{@codenext}、{@coderemove}、{@codeprevious}的反应,{@codeset}或{@codeadd}操作。这提供了快速失败行径,而不是迭代流程中并发修改的情况。
我来浮浅再说一下:
这个modCount,不错知晓为纪录list的变动值。若是你的list里面贯串add7个元素,那么这个变动值便是7.若是是add7个元素,remove1个元素,那么这个值便是8.归正便是修改变动的次数的一个统计值。
而这个值,在使用迭代的时分,会在迭代器运转换传入,赋值给到迭代器Itr里面的里面纪录值,也便是咱们刚刚讲到的expectedModCount值。这样来留意使用的时分,特等外的修改,导致并发的问题。
这样一说,其实咱们报错ConcurrentModificationException的原因就很显然了。
一脱手的情况:
是以在咱们第一次轮回检测,使用foreach语法糖,调用Itr的next智商时,会去调用check智商:
因为如实一脱手人人都是7,检测modCount和expectedModCount值是通过的:
接着,咱们不时触发Itr的next智商,按照平淡,亦然调用了check智商,遵循检测出来运转换传入的list变化纪录数expectedModCount是7,而最新的list的变更纪录数modCount因为在第一次的list.remove触发后,modCount++了,变成了8,是以:
两值不等,抛出造作。
是以上述出现报错ConcurrentModificationException的原因尽头明了,其实便是因为调用了Itr的next智商,而next智商每次实验时,会调check智商。那么不错知晓为,这是foreach语法糖+移除时的锅。
那么咱们就幸免这个语法糖,咱们先来个习尚性编写的for轮回步地:
Listlist=newArrayList;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);intsize=list.size;for(inti=0;i
这样的实验遵循是啥,报错了,IndexOutOfBoundsException数组索引限度极端:
为啥会错啊,原因很浮浅:
ps:cv习尚了,蓝色字体里也曾cv不分了,也不改了,人人领略即可。
是以这个示例报错的原由很浮浅,我编码问题,把size值提前固定为7了,然后list的size是及时变化的。
那么我把size不提前获得了,放在for轮回里面。这样就不会导致i++使i大于list的size了:
Listlist=newArrayList;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(inti=0;i
这样的运行遵循是什么:
固然没报错,可是莫得移除干净,为什么?
其实照旧因为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“元素。
Listlist=newArrayList;list.add("A");list.add("C");list.add("C");System.out.println("未移除前"+list.toString);for(inti=0;i
先看一下遵循,照旧出现移除不干净:
分析:
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,变成了index1,这个家伙都还没被取出来,轮回限定了,它就逃过了检测。是以没被移除干净。
PS:很可能有些看客心里面会想(我YY你们会这样想),平时用的remove是运用index移除的,跟我上头使用的remove(Objecto)还不雷同的,是不是我例子的代码使用智商问题。
关联词并否则,因为这个remove调用的是哪个,其实不是要点,看图:
遵循照旧雷同:
其实这样的for轮回写法,跟list的remove到底使用的是Object匹配移除照旧Index移除,没相联系的。移除不干净是因为轮回i的值跟list的size变动,跳出轮回形成的。
能看到这里的昆玉,繁难了。
那么使用remove这个智商,结合轮回,那就真的没见地移除干净了吗?
行得通的例子:
while轮回:
Listlist=newArrayList;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
Listlist=newArrayList;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);Iteratorit=list.iterator;while(it.hasNext){Stringx=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"));
Listlist=newArrayList;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这个智商,请看源码:
再看newArrayList的构造智商,亦然不允许为空的:
PS:可是这只是构造智商的规定,千万别搞错了,ArrayList是不错存储null元素的。add(null)可莫得说不允许null元素传入。
回到刚刚的话题,那么咱们运行莫得问题的Collections.singleton(null)奈何就没报空指针极端呢?
那是因为复返的是Set,而Set的构造智商亦然允许传入null的:
是以在使用removeAll的时分,想移除null元素,其实只需要传入的联结里面是null元素就不错,也便是说,不错稚子地写成这样,亦然ok的(了解旨趣就行,不是说非得这样写,因为背面还有更好的智商先容):
从一脱手的移除C元素,到当今更特殊极少的移除null元素。
到这里,似乎也曾有了一个了解和了结,remove和removeAll使用起来应该是没啥问题。
可是本篇著作还没限定,越扯越远。
因为我想给人人了解更多,不谎话不时说。
removeIf
这个智商,是javaJDK1.8版块,Collection以偏执子类新引入的。
那既然是新引入的,笃定亦然有原因了,笃定是愈加好用愈加能贬责咱们移除list里面的某元素的痛点了。
咱们径直结合使用步地过一下这个智商吧:
移除list里面的null元素:
list.removeIf(Objects::isNull);
运行遵循:
再来,咱们写的愈加通用极少,照旧移除null元素:
list.removeIf(o->null==o鑫百利娱乐);
运行遵循,莫得问题:
咱们移除的条款更多极少,把C元素和null元素和”“元素都移除了:
list.removeIf(o->null==o