最近在公司项目中需要实现:在两个实体对象不同的列表中,删除A列表中包含B列表,通过判断A中实体和B中实体某一个属性是否相同来删除。期间出现IndexOutOfBoundsException ,ConcurrentModificationException,IllegalStateException等等异常,解决完写一篇记录下。
循序渐进,分析几个常见的遍历删除及其出错的原因:
常见错误方法一:
List<Integer> list = new ArrayList<>(); Collections.addAll(list, 9, 1, 3, 6, 5); int size = list.size(); for (int i = 0; i < size; i++) { Integer value = list.get(i); if (value == 3 || value == 5) { list.remove(i); } }
此时会报IndexOutOfBoundsException
原因:size此刻是一个常量,大小是不变的,删除了其中一个非末尾(最后一个)的元素,当遍历到最后一个元素时,索引大于了list的大小,自然就数组越界了,从中也可以看出remove方法会改变list.size()方法的大小。
常见错误方法二:
for (Integer value : list) { if (value == 1 || value == 3 || value == 6) { list.remove(value); } }此时会抛出 ConcurrentModificationException
原因:ArrayList中有modCount是AbstractList类中的一个成员变量,expectedModCount:表示对ArrayList修改次数的期望值,它的初始值为modCount。每次remove 的时候会调用checkForComodification,检查expectedModCount是否等于modCount,如果不相等则抛出ConcurrentModificationException。而每次的remove,在fastRemove()方法中modCount都会加1,导致两个数不想等。此刻不禁会有疑问,为什么remove(index) 抛的是IndexOutOfBoundsException,而remove(Object)抛的却是ConcurrentModificationException?难道remove(index)没有调用checkForComodification,期待大神解答。
常见错误方法三:
for (int i = 0; i < list.size(); i++) { Integer value = list.get(i); if (value == 1 || value == 3 || value == 6) { list.remove(i); System.out.println(value); } }
此时没有抛异常,但是不能实现我们需要的功能,我们想删除1,3,6,但是结果呈现的是只删除了1和6。为什么会出现这种情况呐?
原因:1的索引为2,当删除了1后,list的长度变化,3变到1的位置,而1删除后索引还是加1,导致3成了落网之鱼,而6的索引是2,所以当索引加到2时,6也就被删除了。
单列表操作正确方法一:
for (int i = 0; i < list.size(); i++) { Integer value = list.get(i); if (value == 1 || value == 3 || value == 6) { list.remove(i); i--; System.out.println(value); } }
单列表操作正确方法二:
for (int i = list.size() - 1; i >= 0; i--) { Integer value = list.get(i); if (value == 1 || value == 3 || value == 6) { list.remove(i); System.out.println(value); } }
单列表操作正确方法三(推荐):
Iterator<Integer> it = list.iterator(); while (it.hasNext()) { Integer value = it.next(); if (value == 1 || value == 3 || value == 6) { System.out.println(value); it.remove(); } }
双列表去重操作方法一(推荐):