遍历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 | 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, 那么就回答不可以, 会抛出并发操作异常.