最近在看Java开发手册时,看到这样一条规定:
下面就来一探究竟,看看为什么会有这样的规定。
Foreach循环(Foreach loop)是计算机编程语言中的一种控制流程语句,通常用来循环遍历数组或集合中的元素。foreach语法格式如下:
for( 元素类型T 元素变量t : 遍历对象obj){
引用了t 的java 语句;
}
以下实例演示了普通for循环和foreach循环使用:
private static void test() {
List<String> names = new ArrayList<String>() {{
add("Hello");
add("World");
add("Good");
}};
System.out.println("foreach循环");
for (String name : names) {
System.out.println(name);
}
System.out.println("普通for循环");
for (int i = 0; i < names.size(); i++) {
System.out.println(names.get(i));
}
}
输出结果如下:
foreach循环
Hello
World
Good
普通for循环
Hello
World
Good
可以看到,使用foreach语法遍历集合或者数组的时候,可以起到和普通for循环同样的效果,并且代码更加简洁。所以,foreach循环也通常也被称为增强for循环。
其实,增强for循环是Java给我们提供的一个语法糖,如果将以上代码编译后的class 文件进行反编译的话,可以得到以下代码:
private static void test() {
List<String> names = new ArrayList<String>() {
{
this.add("Hello");
this.add("World");
this.add("Good");
}
};
System.out.println("foreach循环");
Iterator var1 = names.iterator();
while(var1.hasNext()) {
String name = (String)var1.next();
System.out.println(name);
}
System.out.println("普通for循环");
for(int i = 0; i < names.size(); ++i) {
System.out.println((String)names.get(i));
}
}
可以发现,原来增强for循环是依赖了while循环和Iterator实现的。规范中指出不让我们在foreach循环中对集合元素做add/remove操作,那么,我们尝试着做一下看看会发生什么问题。
private static void test() {
List<String> names = new ArrayList<String>() {{
add("Hello");
add("World");
add("Good");
}};
System.out.println("增强for循环");
for (String name : names) {
System.out.println(name);
if ("Hello".equals(name)) {
names.remove(name);
}
}
}
增强for循环
Hello
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
可以看到抛出了异常,追踪异常中的checkForComodification如下:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这是ArrayList中的检测代码,那么这个modCount和expectedModCount又是什么东西呢?
通过翻源码,我们可以发现:
●● modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
●● expectedModCount是ArrayList中的一个内部类——Itr中的成员变量。expectedModCount 表示这个迭代器期望该集合被修改的次数。其值是在ArrayList.iterator方法被调用的时候初始化的。只有通过迭代器对集合进行操作,该值才会改变。
●● Itr是一个Iterator的实现,使用ArrayList.iterator方法可以获取到的迭代器就是Itr类的实例。
他们之间的关系如下:
class ArrayList{
private int modCount;
public void add();
public void remove();
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
}
public Iterator<E> iterator() {
return new Itr();
}
}
remove方法核心逻辑如下:
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
可以看到,它只修改了modCount,并没有对expectedModCount做任何操作。
之所以会抛出ConcurrentModificationException异常,是因为我们的代码中使用了增强for循环,而在增强for循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被删除/ 添加
,就会抛出一个异常,用来提示用户,可能发生了并发修改。
因此当我们确实有需求需要删除其中一部分元素时,因该如java手册建议的那样使用Iterator进行操作。
private static void test() {
List<String> names = new ArrayList<String>() {{
add("Hello");
add("World");
add("Good");
}};
Iterator iterator = names.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("Hello")) {
iterator.remove();
}
}
}