foreach介绍
foreach是Java1.5引入的语法糖,语法糖写法简洁明了,容易被人理解,更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,但是不了解语法糖实现的原理很容易导致编写的代码运行时错误。
错误的例子
public static void main(String[] args) {
List<String> nums = new ArrayList<String>();
nums.add("1");
nums.add("2");
nums.add("3");
for (String num : nums) {
if (num.equals("3")) {
nums.remove(num);
}
}
}
此段代码在编码阶段工具是不会报错的,但是运行时就会报出java.util.ConcurrentModificationException错误,我们先通过反编译来查看foreach会被Java编译器如何实现,关于如何反编译请查看https://blog.csdn.net/ren365880/article/details/108227239。
public static void main(String args[])
{
ArrayList arraylist = new ArrayList();
arraylist.add("1");
arraylist.add("2");
arraylist.add("3");
Iterator iterator = arraylist.iterator();
do
{
if(!iterator.hasNext())
break;
String s = (String)iterator.next();
if(s.equals("3"))
arraylist.remove(s);
} while(true);
}
通过查看反编译后的文件可以看到,Java底层使用的迭代器实现的foreach。
一步步发现为什么报错
先看一下报的什么错,在什么文件哪一行报的错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.proxy.Test.main(Test.java:15)
可以看到,调用了ArrayList的checkForComodification方法报错,定位到错误行
可以看到,当变量modCount != expectedModCount的时候,就会抛ConcurrentModificationException。
modCount和expectedModCount变量是什么意思呢?
modCount:
此列表在结构上进行修改的次数。结构修改是更改列表大小的修改。
该字段由iterator和listIterator方法返回的迭代器和列表迭代器实现使用。如果此字段的值意外更改,则迭代器(或listiterator)将抛出ConcurrentModificationException,以响应下一个,移除,上一个,设置或添加操作 。 提供了快速故障行为以面对迭代期间的并发修改。
expectedModCount
期望被修改的次数。
这两个参数什么时候设置的呢?
首先先看ArrayList的调用的迭代器:
它返回了一个Itr类的示例,Itr是如何实现的呢?
可以看到,在初始化时 设置 expectedModCount = modCount;并且在获取迭代器下一个是进行了checkForComodification()方法验证。
为什么add/remove后2个参数就不一致了?
先看删除方法是如何实现的:
可以看到只是modCount增加了。再看添加:
也是只增加了modCount,modCount != expectedModCount在next方法检查时就会报错!