忏悔篇移除List中的元素引发的异常
遍历集合出现的问题
之前遇到对List进行遍历删除的时候,出现来一个 ConcurrentModificationException 异常,可能好多人都知道list遍历不能直接进行删除操作,但是你可能只是跟我一样知道结果,但是不知道为什么不能删除,或者说这个报错是如何产生的,那么我们今天就来研究一下。一、异常代码
我们先看下这段代码,你有没有写过类似的代码 public static void main(String[] args) { List list = new ArrayList<>(); System.out.println("开始添加元素 size:" + list.size()); for (int i = 0; i < 100; i++) { list.add(i + 1); } System.out.println("元素添加结束 size:" + list.size()); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Integer next = iterator.next(); if (next % 5 == 0) { list.remove(next); } } System.out.println("执行结束 size:" + list.size()); }
「毫无疑问,执行这段代码之后,必然报错,我们看下报错信息。」
我们可以通过错误信息可以看到,具体的错误是在 checkForComodification 这个方法产生的。二、ArrayList源码分析
首先我们看下 ArrayList 的iterator 这个方法,通过源码可以发现,其实这个返回的是ArrayList 内部类的一个实例对象。public Iterator iterator() { return new Itr(); }
我们看下 Itr 类的全部实现。private class Itr implements Iterator { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { Objects.requireNonNull(consumer); final int size = ArrayList.this.size; int i = cursor; if (i >= size) { return; } final Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) { throw new ConcurrentModificationException(); } while (i != size && modCount == expectedModCount) { consumer.accept((E) elementData[i++]); } // update once at end of iteration to reduce heap write traffic cursor = i; lastRet = i - 1; checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
「参数说明:」
cursor : 下一次访问的索引;
lastRet :上一次访问的索引;
expectedModCount :对ArrayList修改次数的期望值,初始值为modCount ;
modCount : 它是AbstractList 的一个成员变量,表示ArrayList 的修改次数,通过add 和remove 方法可以看出;
「几个常用方法:」
hasNext() :public boolean hasNext() { return cursor != size; }
如果下一个访问元素的下标不等于 size ,那么就表示还有元素可以访问,如果下一个访问的元素下标等于size ,那么表示后面已经没有可供访问的元素。因为最后一个元素的下标是size()-1 ,所以当访问下标等于size 的时候必定没有元素可供访问。
next() :public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }
注意下,这里面有两个非常重要的地方, cursor 初始值是0,获取到元素之后,cursor 加1,那么它就是下次索要访问的下标,最后一行,将i 赋值给了lastRet 这个其实就是上次访问的下标。
此时, cursor 变为了1,lastRet 变为了0。
最后我们看下 ArrayList 的remove() 方法做了什么?public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
「重点:」
我们先记住这里, modCount 初始值是0,删除一个元素之后,modCount 自增1,接下来就是删除元素,最后一行将引用置为null 是为了方便垃圾回收器进行回收。三、问题定位
到这里,其实一个完整的判断、获取、删除已经走完了,此时我们回忆下各个变量的值:
cursor : 1(获取了一次元素,默认值0自增了1);
lastRet :0(上一个访问元素的下标值);
expectedModCount :0(初始默认值);
modCount : 1(进行了一次remove 操作,变成了1);
不知道你还记不记得, next() 方法中有两次检查,如果已经忘记的话,建议你往上翻一翻,我们来看下这个判断:final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
当 modCount 不等于expectedModCount 的时候抛出异常,那么现在我们可以通过上面各变量的值发现,两个变量的值到底是多少,并且知道它们是怎么演变过来的。那么现在我们是不是清楚了ConcurrentModificationException 异常产生的愿意呢!
「就是因为, list.remove() 导致modCount 与expectedModCount 的值不一致从而引发的问题。」四、解决问题
我们现在知道引发这个问题,是因为两个变量的值不一致所导致的,那么有没有什么办法可以解决这个问题呢!答案肯定是有的,通过源码可以发现, Iterator 里面也提供了remove 方法。public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
你看它做了什么,它将 modCount 的值赋值给了expectedModCount ,那么在调用next() 进行检查判断的时候势必不会出现问题。
那么以后如果需要 remove 的话,千万不要使用list.remove() 了,而是使用iterator.remove() ,这样其实就不会出现异常了。public static void main(String[] args) { List list = new ArrayList<>(); System.out.println("开始添加元素 size:" + list.size()); for (int i = 0; i < 100; i++) { list.add(i + 1); } System.out.println("元素添加结束 size:" + list.size()); Iterator iterator = list.iterator(); while (iterator.hasNext()) { Integer next = iterator.next(); if (next % 5 == 0) { iterator.remove(); } } System.out.println("执行结束 size:" + list.size()); }
「建议:」
另外告诉大家,我们在进行测试的时候,如果找不到某个类的实现类,因为有时候一个类有超级多的实现类,但是你不知道它到底调用的是哪个,那么你就通过 debug 的方式进行查找,是很便捷的方法。五、总结
其实这个问题很常见,也是很简单,但是我们做技术的就是把握细节,通过追溯它的具体实现,发现它的问题所在,这样你不仅仅知道这样有问题,而且你还知道这个问题具体是如何产生的,那么今后不论对于你平时的工作还是面试都是莫大的帮助。
浙江有乌镇安徽有乌江浙江乌镇是世界互联网中心安徽乌江有座室内最大的书画艺术广场将成为世界书画艺术集散中心浙江乌镇,原来在文人墨客眼中,仅是一座古老的江南水乡。而如今,小小的浙江乌镇却成为世界闻名的互联
1千块人民币可兑换22万缅甸元,在缅甸能做什么?当地美女告诉你缅甸是一个距离我国比较近,拥有众多特色的国家。由于它的物价水平比较低廉,因此国内有许多游客会选择去缅甸游玩。(此处已添加小程序,请到今日头条客户端查看)一般在缅甸游玩的时候,可以乘
型月好忙!魔法使之夜HD2022年登陆switch平台型月TYPEMOON近段时间很高产,公司宣布魔法使之夜将移植到switch和PS4平台,预计2022年发售。根据介绍本次魔法使之夜移植将会HD化,另外还将为游戏增加全语音。目前从预
csgo进阶小技巧,不再当人肉靶子,告别白给,你值得拥有今天就讲讲如何从csgo靶子进阶成大佬,一起来学学csgo一定要掌握的技巧。枪法枪法不必多说,枪法是玩好FPS类游戏的基础,如果枪法不行建议直接别再接触FPS类型的游戏了,。如果有
王者荣耀英雄的前世今生入梦之灵梦奇篇十多年前,江东名门乔氏诞生一对双生女婴时,曾掀起一场轩然大波。对精于魔道的家族而言,双胞胎是不吉利的。最终,双胞胎中的妹妹被送往稷下寄养。也许因为缺少家族的束缚,小乔并没有成为姐姐
高知姥姥培养两代学霸,她说小学是关键,功夫主要下在这里文艾琳妈咪日记我们身边是不是总有这样的孩子?明明见他们整天该玩就玩,该乐就乐,心思似乎没放在学习上,但每次考试总能毫不费力地考取高分?而有的孩子每天刻苦学习至深夜,但成绩却平平?这
小孩教育,是要均衡发展,还是要扬长避短?1。hr现实教育中,均衡发展是主流。家长们认为,孩子不能有明显的短板,否则这会制约他一生的发展。体育要好,身体是革命的本身音乐也不错,将来拥有一门乐器爱好,有一个优雅的兴趣画画可以
孩子叫王者荣耀可以,叫塞外女侠为什么不行?孩子叫王者荣耀可以,叫塞外女侠为什么不行?给娃起名是个技术活,好的名字让人印象深刻,介绍起来也特有面子。差的名字,要么听过就忘,要么难于启齿。生活中,还有一些介于好名字和差名字之间
上帝打翻的颜料盘,喀纳斯自驾游喀纳斯位于新疆阿尔泰山中段,地处中国与哈萨克斯坦俄罗斯蒙古国接壤地带,景区面积1。03万平方公里。喀纳斯在蒙古语里意为美丽富饶神秘莫测,这里集秀丽的高山河流森林湖泊草原等奇异的自然
药名相似中药甄别诀药名相似中药甄别诀胡军药名相似易混淆,此诀牢记莫忘掉。独活羌活名相似,二者皆可祛风湿,止痛还可以解表,风寒湿痹表证疗。羌活药性较为烈,发散力强性上窜,风寒湿痹在上身,羌活要比独活好
男士护肤品是智商税吗?有哪些真正好用口碑好的男士护肤品推荐?但随着现代人们受教育程度的提高,我们逐步开始认识到,不论男女,一张干净整洁的脸庞,或者说对自己仪容仪表仪态的重视,不仅是一个人对自我要求的象征,也是对他人的一种尊重。那么针对男士的
男人结扎和女人结扎有什么区别?看完后,这几个好处女人真没法比夫妻如果已经有了孩子,或者因为身体的原因,不适合再生育,那么肯定要选择避孕的方式来避免麻烦。如果夫妻俩不把避孕的措施做好,那自然会让妻子遭受身体上的伤害。相信大家也看过很多避孕的相
孩子的阅读,当然孩子说了算今天的文,来和大家聊几个群内这几天聊的比较多的两个话题,这两个话题我在群内是有分享过我的看法的,观点比较个人,但我还是想分享出来和你一起聊聊,因为最近有太多父母为此而焦虑了。01h
抑郁症群体已瞄向青少年,错误的教育方式是根源,别让孩子被强迫作为家长都希望自己的孩子都能成为所谓别人家的孩子,望子成龙,望女成凤的心愿,因此就会给孩子安排各种各样认为对孩子有帮助的事项,甚至会用强制的手段要求孩子去做,但是往往效果不佳,还会
亲们看过来如何让孩子拥有高情商和高财商?带孩子去吃饭要花钱,送孩子去上兴趣班要花钱,小孩子自己也在花钱那么,你为什么不跟孩子讲讲钱呢?给孩子讲钱,并不是要让孩子成为财迷,而是让他们知道,钱是什么?钱的价值是什么?钱的作用
读书伴我成长读书,是我成长的摇篮,伴我度过一个个喜怒哀乐读书,是我成长中最好的朋友,伴我度过一个个美好时光读书,是一把金钥匙,打开一道道知识的大门。读书,伴我成长。说到读书,我的第一本书,是幼
认识情绪愤怒说出表达情绪减少愤怒甚至情绪消失恐惧不会恐惧就不会保护自己导致生命收到威胁。消极情绪求生存(生存模式)积极情绪求发展爱是世界上最伟大的力量,爱会创造奇迹!(奶奶与爸爸的做法,保
多动症孩子最不能忽略的问题理解能力差对于多动症孩子来说,最难的还停留在第一步听不进去话。别人跟他说话的时候他有时候大脑空白,即便是面对面他也不理解甚至记不住对方说的话,一句话进入他的大脑是需要有个过程的。其实孩子也不
2022卡塔尔世界杯夺冠热门球队英格兰队很强?足坛盛行一句俗语,英格兰很强,意大利很弱这到底是戏谑之词,还是事出有因。在我看来,两国的青训培养思维差异是主要原因,英超强调个人能力,意甲更强调整体性。这届英格兰的球员身价冠绝32
被99玩家严重低估的神装,能让亚瑟后期不乏力,版本之子崛起陌陌聊游戏,给您带来最新最快的王者资讯!王者荣耀七周年庆的活动已经开始了。在周年庆的活动中,不要忘记有加星卡哦。那么问题来了,用什么英雄去打排位,能够起到很好的加星呢?如果还没有上
详解机器视觉之光源基础与选型技巧一套完整的视觉检测系统主要包含图像采集部分和图像分析部分,而图像采集部分主要有工业相机工业镜头以及机器视觉光源承担,今天我们主要介绍机器视觉光源的相关基础知识及选型技巧。首先我们需
卢卡谈脑后妙传训练中常做些愚蠢的动作可能在比赛中是可行的直播吧10月28日讯今日NBA常规赛,独行侠客场加时力克篮网,赛后东契奇接受了媒体采访。比赛第三节,东契奇突入内线后面对多人包夹,为队友克莱伯送出一记脑后妙传,东契奇对此表示我真的