第46条 for-each循环优先于传统的for循环

迭代器循环的缺点

如下面代码及输出结果所示,本来期望输出的是Face枚举值的笛卡尔积、36种结果,实际上输出只有6种结果。原因就是i和j本质上都是同一个引用对象,所以遍历过程中、两者是同时变化的。

enum Face {ONE, TWO, THREE, FOUR, FIVE, SIX}
Collection<Face> faces = Arrays.asList(Face.values());
for (Iterator<Face> i = faces.iterator(); i.hasNext();) {
	for (Iterator<Face> j = faces.iterator(); j.hasNext();) {
    	System.out.println(i.next() + " " + j.next());
	}
}

输出结果

使用for-each循环

for (Face face : faces) {
   	for (Face face1 : faces) {
    	System.out.println(face + " " + face1);
	}
}

优点:完全隐藏迭代器或索引变量,避免了混乱和出错的可能,代码简洁性高、bug率低。这种模式同样适用于集合和数组。
同样地,任何实现了Iterable接口的对象,通可以通过for-each循环遍历。

不能使用for-each循环的三种场景

  • 过滤:若需要遍历集合并删除其中的某个元素,则应该使用显示的迭代器
  • 转换:若需要遍历列表或数组,并取代部它部分或全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值。
  • 平行迭代:如果需要并行地遍历多个集合,就需要显示地控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移。
enum Number {1, 2, 3, 4, 5, 6}
List<Number> numbers = Arrays.asList(Number.values());
for(int i = 0; i < 6; i ++) {
	System.out.println(faces.get(i) + " - " + numbers.get(i));
}

for循环内部禁止remove操作

如上述不能使用for-each循环的三种场景的第一种场景,其实就是禁止在for循环内部使用remove操作。

 List<String> list = new ArrayList<String>();
 list.add("1");
 for (String item : list) {
	if ("1".equals(item)) {
		list.remove(item);
    }
} 
System.out.println(list.toString());
// 抛出异常 
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
	at java.util.ArrayList$Itr.next(ArrayList.java:857)
	at com.zhc.generic.TestForeach.main(TestForeach.java:27)
     

而使用迭代器遍历的时候,使用remove操作是正常的

List<String> list = new ArrayList<String>();
list.add("1");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
	String item = iterator.next();
    if("1".equals(item)){
    	iterator.remove();
     }
}
System.out.println(list.toString());
// 输出空数组[]

那么这是为什么呢?
我们看上面的异常是从java.util.ArrayList$Itr.checkForComodification方法内抛出的,所以我们来看看源码。

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
	protected transient int modCount = 0; // 集合被修改次数
}

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;
	final void checkForComodification() {
    	if (modCount != expectedModCount)
        	throw new ConcurrentModificationException();
  	}
 }

其实,就是预期的集合被修改次数expectedModCount与实际的被修改次数modCount不相等,那么我们再来看看这两个参数都会在哪里被修改。其中迭代器中的remove方法会调用ArrayList的remove(int index)方法,该方法内会看到会修改modCount,然后会将被修改过的modCount赋值给expectedModCount,这样就能保证两者始终是同步被修改的、最终的值也是相等的,因此在遍历集合过程中删除依然能正常运行。

private class Itr implements Iterator<E> {
	public void remove() {
		if (lastRet < 0)
	    	throw new IllegalStateException();
	    	checkForComodification();
		try {
	    	ArrayList.this.remove(lastRet);
	        cursor = lastRet;
	        lastRet = -1;
	        expectedModCount = modCount;
	     } catch (IndexOutOfBoundsException ex) {
	     	throw new ConcurrentModificationException();
	     }
	}
}

public E remove(int index) {
	rangeCheck(index);
	modCount++;
    E oldValue = elementData(index);
    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
	return oldValue;
    }

我们继续看ArrayList的remove(Object o)方法,该方法内部调用的fastRemove方法内部同样会修改modCount,但是与迭代器的remove方法的最明显的差别就是没有修改expectedModCount的值。因此,如果在foreach遍历集合过程中执行remove操作,就会抛出运行时异常ConcurrentModificationException。

public boolean remove(Object o) {
	if (o == null) {
   		for (int index = 0; index < size; index++)
       		if (elementData[index] == null) {
            	fastRemove(index);
                return true;
            }
	} else {
    	for (int index = 0; index < size; index++)
        	if (o.equals(elementData[index])) {
            	fastRemove(index);
                return true;
           	}
    }
	return false;
}

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值