迭代器循环的缺点
如下面代码及输出结果所示,本来期望输出的是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
}