Iterator 仅用于遍历集合,本身并不提供像集合类那样装对象的能力。Iterator 是个借口,如果需要创建其对象,必须有一个被迭代的集合,没有集合的 Iterator 没有存在的价值。
所以说,Iterator 必须依附于 Collection 对象,有一个 Iterator 对象,肯定就有一个与之关联的 Collection 对象。文章《Java遍历HashSet为什么输出是有序的》一文中开始有个例子,可以看到在迭代 HashSet 的过程中对迭代遍历进行赋值,但最后输出时发现集合无任何改变。原因是在对集合进行迭代的过程中,Iterator 并不是把集合中的元素本身传递给迭代变量,而是把值传给迭代变量。
因此,在迭代集合的过程中,如果对集合中的元素进行操作,则会抛出 ConcurrentModificationException 异常,比如以下代码:
- package com.menglanglang.java.collection;
- import java.util.*;
- /**
- * Description:
- * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
- * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
- * <br/>This program is protected by copyright laws.
- * <br/>Program Name:
- * <br/>Date:
- * @author Yeeku.H.Lee kongyeeku@163.com
- * @version 1.0
- */
- public class IteratorErrorTest
- {
- public static void main(String[] args)
- {
- // 创建集合、添加元素的代码与前一个程序相同
- Collection books = new HashSet();
- books.add("轻量级Java EE企业应用实战");
- books.add("疯狂Java讲义");
- books.add("疯狂Android讲义");
- // 获取books集合对应的迭代器
- Iterator it = books.iterator();
- while(it.hasNext())
- {
- String book = (String)it.next();
- System.out.println(book);
- if (book.equals("疯狂Android讲义"))
- {
- // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
- books.remove(book);
- }
- }
- System.out.println(books);
- }
- }
输出异常信息如下:
疯狂Android讲义
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at com.menglanglang.java.collection.IteratorErrorTest.main(IteratorErrorTest.java:27)
Iterator 使用的是快速失败(fail-fast)机制,该机制一旦监测到在迭代过程中集合中的元素被修改,立即抛出 ConcurrentModificationException 异常。但如果把上面的代码中,判断部分改为“疯狂Java讲义”,即当判断迭代元素为“疯狂Java讲义”时,从集合中删除“疯狂Java讲义”元素,则可以看到如下结果:
疯狂Android讲义
轻量级Java EE企业应用实战
疯狂Java讲义
[疯狂Android讲义, 轻量级Java EE企业应用实战]
可以看到删除成功了,《疯狂Java讲义》中说到只有删除集合中特定的元素才会出现不报错的情况,是由集合类的实现代码决定的,不能就轻易地下集合迭代过程中可以修改其中的元素的结论。
我又拿文章《Java遍历HashSet为什么输出是有序的》一文中后面的例子试了试,该例子只是在每个串前加了个“孟”字。
结果是可以删除“孟轻量级Java EE企业应用实战”的元素,而删除“孟疯狂Java讲义”则报错。联系前面文章中提到的有序问题,我发现当删除输出中的最后一个元素时,是可以删除的,除了最后一个元素,其它元素的删除都抛异常。所以怀着好奇心,看了下 Java 源码中,HashSet 类对 remove() 方法的实现,源码如下:
- public boolean remove(Object o) {
- return map.remove(o)==PRESENT;
- }
而 PRESENT 常量的定义如下:
private static final Object PRESENT = new Object();
为何集合 map 当其 remove 输出时的最后一个元素时,可以返回 true,而 remove 其它元素时,直接抛出异常?
有人解释道:在执行了 remove 方法之后,再去执行循环 iter.next() 的时候,报java.util.ConcurrentModificationException,如果 remove 的是最后一条,就不会再去执行 next() 操作了,所以不会报错。
其实,看其 AbstractList.java 源码,主要有如下方法:
- private void checkForComodification() {
- if (this.modCount != l.modCount)
- throw new ConcurrentModificationException();
- }
而在 next(),add(),remove(),size() 等方法中都会调用该验证方法,如果 if 中的判断成立,则立刻抛出异常,具体详细请参看源码。
从本质上解释原因:
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来集合的单链索引表,当原来的集合数量发生变化时,这个索引表的内容不会同步改变,当索引指针往后移动的时候就找不到要迭代的对象,按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的,但可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
共勉~