今天一位朋友在用迭代器时很郁闷为什么会会报Java.util.ConcurrentModificationExceptiond 异常, 于是写下这篇博客,想详细的讲讲Java里面 的迭代器.
Iterator简单的来说就是遍历, 遍历什么? 遍历集合元素等.
Iterator接口共有四个方法:
public interface Iterator<E>{
boolean hasNext():如果被迭代的集合还元素没有被遍历,则返回true。
Object next():返回集合里下一个元素。
void remove() :删除集合里上一次next方法返回的元素
void forEachRemaining(Consumer action),这是Java 8为Iterator新增的默认方法,该方法可使用Lambda表达式来遍历集合元素。
}
}
如果只是普通的迭代输出,那么不会出现什么问题.但是在现实需求中这往往没有什么用,我们还需要做一些额外的操作,比如说
add/remove等。 但是这样做很可能会出现一些很尴尬的事情, 下面我给出一段正确的迭代删除代码:
import java.util.*;
public class TestIterator
{
public static void main(String[] args)
{
// 创建集合、添加元素
Collection<String> books = new ArrayList();
books.add("测试1");
books.add("测试2");
books.add("测试3");
Iterator it = books.iterator();
while(it.hasNext())
{
// it.next()方法返回的数据类型是Object类型,因此需要强制类型转换
String book = (String)it.next();
System.out.println(book);
if (book.equals("测试2"))
{
// 从集合中删除上一次next方法返回的元素
it.remove();
}
}
System.out.println(books);
}
}
上面的代码经过测试是可以正确删除元素的,有疑问的代码估计也就是这一句了
Iterator it = books.iterator();
对list本身进行删除就不行了, 对于这种莫名其妙的问题,我们从源码入手, 首先根据上面的我们先看看ArrayList他是怎么返回Iterator实例的,最终源码如下:
在ArrayList类中有一个私有类Private Itr 我们注意到expectedModCount这个字段,他的初始值是等于modCount的。 那modCount又是干啥的呢 最直接的。 看源码:
我们从源码可以对集合的每一次 add / remove 都会触发一下这个字段,而上面Itr的remove 不管在删除还是添加都会触发一个方法(csdn上截图太麻烦我直接写了 ) checkForComodification();这个方法, 大家可以看看上面的截图而这个 checkForComodification();方法中只有一个功能
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
那就是抛出异常~~~~
所以大家应该懂了那段错误的代码为什么会错误了吧。。 因为二个字段不相等啊。 没有同步啊。
如果你问我为什么要这么做,我只能这么跟你说:
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性