最近在看Java开发手册时,看到这样一条规定:

loop 的使用 java 博客 for loop java_System


下面就来一探究竟,看看为什么会有这样的规定。

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();
            }
        }
    }