foreach循环(增强for循环)的坑 - ConcurrentModificationException

最近在看《阿里巴巴Java开发手册》时,(五)集合处理-7【强制】不要在foreach循环里进行元素的remove/add操作。这一条规则中,有一段非常有意思的代码:


> List<String> list = new ArrayList<>(); list.add("1"); list.add("2");
> 
> for (String item : list) {
>     if ("1".equals(item)) {
>         list.remove(item);
>     } 
> }

执行这段代码,没有任何问题,foreach循环不会抛出任何异常,而且元素移除成功。但是,当我们把if ("1".equals(item))改成if ("2".equals(item))时,程序会抛出ConcurrentModificationException。如下图所示:
在这里插入图片描述
通过分析foreach实现机制以及抛出的代码行数,很容易得知其原因。

foreach实现机制

我们知道,foreach是Java的一个语法糖,通过反编译测试类生成的代码,我们知道它实际上是由Iterator实现的。反编译的结果如下:


> List<String> list = new ArrayList(); list.add("1"); list.add("2");
> Iterator var2 = list.iterator();
> 
> while (var2.hasNext()) {
>     String item = (String)var2.next();
>     if ("2".equals(item)) {
>         list.remove(item);
>     }
> }

结合程序抛出的异常栈信息,我们可以得知,异常是在String item = (String)var2.next();这一行抛出的(只有这一行调用了next()操作)。

异常原因

找到报错的代码行,我们再来分析报错的原因。既然在String item = (String)var2.next();报错,说明while循环的条件是通过了。也就是说,我们在移除了最后一个元素后,迭代器竟然并未感知到,hasNext()依然返回true。我们来看看ArrayList.IteratorhasNext()是如何实现的:


> private class Itr implements Iterator<E> {
>     int cursor;       // index of next element to return
>     int lastRet = -1; // index of last element returned; -1 if no such
>     int expectedModCount = modCount;   // 修改次数,modCount来自于外部的ArrayList类;
>                                        // 而expectedModCount则由Itr记录
>     ...
>     public boolean hasNext() {
>         return cursor != size;   // size来自于外部的ArrayList类
>     }
>     ... 
> }

ArrayList.iterator().hasNext()的实现很简单,就是判断当前下标是否与数组大小相同。这就说明,当删除ArrayList最后一个元素时,cursor != size。为什么会这样呢?我们再来看一下next()的实现:

> private class Itr implements Iterator<E> {
>     int cursor;       // index of next element to return
>     int lastRet = -1; // index of last element returned; -1 if no such
>     int expectedModCount = modCount;   // 修改次数,modCount来自于外部的ArrayList类;
>                                        // 而expectedModCount则由Itr记录
>     ...
>     @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];
>     }
>     ...
>     final void checkForComodification() {
>         if (modCount != expectedModCount)
>             throw new ConcurrentModificationException();
>     }
> }

我们看cursor值 的变化,实际上,cursor的值在每次进行元素值对比前,就已经跳到下一个位置。当我们删除第1个元素后,cursor的值是1,与当前ArrayList.size(1)是相等的,hasNext()返回falsewhile循环结束;而当我们删除第2个元素时,cursor的值是2,与当前ArrayList.size(1)不相等,因此hasNext()返回true
我们再看next()为什么抛出异常。ArrayList.Itrnext()操作中,首先检查modCount是否等于expectedModCount,如果不相等,则抛出ConcurrentModificationException,程序中的异常也正是在此处抛出的。expectedModCountArrayList.Itr维护,而我们程序中用ArrayList.remove()移除元素,修改的是ArrayList.modCount,这两个值显然不相等(expectedModCount == 0而modCount == 1)。正是由于hasNext()错误地返回了true,导致我们调用next()而抛出了异常。

结论

从上文的分析中我们可以得出结论,由于foreach语法糖的特性,它并不是严格意义上的java语法,不能像Java标准语法那样灵活地使用。从其生成的代码来看,foreach循环不能进行元素的修改操作,而只能进行元素的遍历操作。

你可能感兴趣的:(Coding)