遍历 List 集合时出现的 3 种异常
参考:
http://blog.csdn.net/izard999/article/details/6708738
http://bbs.itheima.com/thread-41997-1-1.html
异常 1
异常: java.util.ConcurrentModificationException (并发修改异常)
link: http://bbs.itheima.com/thread-41997-1-1.html 此贴描述了第一种异常.
当我们在使用 foreach 这个增强型 for 循环遍历 List 时,除了 System.out.println();
打印可以之外,其他 add, remove 等操作会发生异常 (执行set方法不会抛出异常
).
1 | public class AAA { |
如上代码在增强 for 循环遍历时执行 add 方法会抛出
1 | D:\Java\jdk1.8.0_112\bin\java |
ConcurrentModificationException 翻译为:并发修改异常.
这就代表同时发生了一个操作.
那么借由上面链接中的帖子来解释说在使用 foreach 遍历的时候,其实是通过一个迭代器进行迭代,而在迭代的过程中不允许对 List 集合进行增删之类的操作.
下面是发生其异常的原因,也可以参考: http://bbs.itheima.com/forum.php?mod=viewthread&tid=41997&page=1&authorid=75804 这里的解释.
1 | at java.util.AbstractList$Itr.checkForComodification(Unknown Source) |
java.util.AbstractList$Itr 中 $ 符号后面的 Itr 表示 Itr 是 AbstractList 中的一个内部类.
在 AbstractList$Itr 这个类中实现了 Iterator 接口,当使用增强的 for 循环时,
应该是使用迭代器进行迭代了,如果你在这期间使用了 add 或 remove 方法的话,在 ArrayList 类中执行了这样的代码
1 | public boolean add(E e) { |
add 方法中调用了 ensureCapacity 方法.
1 | public void ensureCapacity(int minCapacity) { |
add 和 remove 方法都引起了 AbstractList$Itr 中的 modCount 属性增加值,然后当下一次遍历 List, 迭代器调用 next 方法时调用了 checkForComodification () 方法.
1 | public E next() { |
但 checkForComodification()
方法进行了 modeCount 和 expectedModCount (翻译为:预期修改次数) 判断,发现不一致,所以抛出了异常.
1 | final void checkForComodification() { |
异常 2
异常: java.lang.IllegalStateException (非法状态异常)
情景如下:
我们知道 Arrays.asList(T ... t);
可以将一个数组变成一个 List.
代码如下:
1 | @Test public void testRemove () { |
在执行到 it.remove();
时会触发异常.
首先 Arrays.asList("fd", "fds", "2341f");
会返回一个 List, 那么这个 List 是什么类型呢?通过查看源代码可以发现
1 | public static <T> List<T> asList(T... a) { |
看起来没什么问题,但是这里有必要说明一下,此 ArrayList 非 java.util.ArrayList
这个 ArrayList. what??? 难道还有两个 ArrayList, 是的.. 那么我们看一下这个 ArrayList 到底是哪里的,Ctrl + T 按下发现这里的 ArrayList 原来是 Arrays 这个类中维护的一个内部类,当然他也是继承自AbstractList
wtfuck…jdk 大佬真会玩。但此 ArrayList 中并没有提供 Iterator () 方法,所以调用 Iterator 方法还是执行的父类 AbstractList 中的 Iterator 方法,
在 java.util.AbstractList$Itr 中我们可以看到 next 和 remove 方法.
1 | /** |
由于这里的 lastRet 初始值为 - 1, 所以执行 remove 方法时触发异常.
异常 3
异常: java.lang.UnsupportedOperationException (不支持的操作异常)
代码如下:
1 | public void testRemove () { |
这里同样是使用 java.util.AbstractList$Itr 迭代器进行获取。但为什么和上面的情况不一样呢,也就是为什么没有触发 java.lang.IllegalStateException
异常,原因是因为我们在 remove 之前调用了 next () 方法,改变了 lastRet 的值,绕过了 remove 方法前面的 if 判断.
1 | /** |
1 | if (lastRet < 0) |
当跳出这个判断后,执行 AbstractList.this.remove(lastRet);
这一句的时候,this 代表是迭代器也就是 java.util.AbstractList$Itr
, 这里的 remove 方法是 java.util.AbstractList#remove 中的方法,这个方法什么也没做,直接抛出了一个异常。因此上面的代码抛出异常.
1 | public E remove(int index) { |
总结
三种异常都发生在迭代器之上.
其中第一种最常见,也是最容易犯的一种.
原因在于一边在遍历,一边在修改,导致底层实现代码出现错误,从而出现异常.
第二和第三则在于 Arrays.asList () 方法所产生的 ArrayList 并不是 java.util.ArrayList, 而是 Arrays 内部类中的 ArrayList. 而这个内部 ArrayList 是没有覆盖父类 java.util.AbstractList 的 add 和 remove 方法的 (但覆盖了 set get, 所以这 2 个方法不受影响), 所以不管是通过获取这个 List 的迭代器来进行 remove (迭代器没有提供 add 方法) 操作 (上面说了迭代器最后调用 remove 方法还是 java.util.AbstractList 提供的方法), 还是调用这个 List 本身继承过来的 add/remove 方法进行操作,最后都是调用的父类中的方法,而父类中的方法,就只有一行抛出异常的代码。所以会发生异常.
我们在开发时,只需要记住。在通过迭代器的方法遍历集合时不要对其进行 remove 和 add 操作就可以了。至于 set 方法是可以的.
其它
有的公司面试时会问到这样的问题,所以这里记录一下分享给大家,也算是本人一个学习记录分享.
面试的时候先看面试给你的是通过 Arrays.asList () 方式获取的集合还是直接 new ArrayList () 的方式,如果是前者,那么可以使用
google 上对怎么解决 ConcurrentModificationException 的方案已经很多,例如用 Collections.synchronizedCollection () 去同步集合,但是这样可能会影响效率,JDK5 之后 concurrent 包里面有个 CopyOnWriteArrayList, 这个集合迭代的时候可以对集合进行增删操作,因为迭代器中没有 checkForComodification!
这种方式进行删除,其次不管是增强型 for 遍历还是普通 for 遍历都不可以删除,因为前者的 ArrayList 是调用的父类中的 remove 方法,会抛出不支持操作异常.
如果是后者,那么可以你关心的就是遍历的时候是否通过迭代器的方式,也就是增强型 for 循环,如果是普通 for 遍历方式,我想面试官不会问你 - -. 如果是增强型 for, 那么就回答不可以,会抛出并发操作异常.