现在位置: 首页 > 博客文章 > 电脑相关 > IT开发 > 开发语言 > Java > 正文
Iterator在迭代中删除元素抛异常
2016年12月14日 16:11:21 Java ⁄ 共 3033字 暂无评论 ⁄ 被围观 3,217次

Iterator 仅用于遍历集合,本身并不提供像集合类那样装对象的能力。Iterator 是个借口,如果需要创建其对象,必须有一个被迭代的集合,没有集合的 Iterator 没有存在的价值。

所以说,Iterator 必须依附于 Collection 对象,有一个 Iterator 对象,肯定就有一个与之关联的 Collection 对象。文章《Java遍历HashSet为什么输出是有序的》一文中开始有个例子,可以看到在迭代 HashSet 的过程中对迭代遍历进行赋值,但最后输出时发现集合无任何改变。原因是在对集合进行迭代的过程中,Iterator 并不是把集合中的元素本身传递给迭代变量,而是把值传给迭代变量。

因此,在迭代集合的过程中,如果对集合中的元素进行操作,则会抛出 ConcurrentModificationException 异常,比如以下代码:

Code   ViewPrint
  1. package com.menglanglang.java.collection;
  2. import java.util.*;
  3. /**
  4.  * Description:
  5.  * <br/>网站: <a href="http://www.crazyit.org">疯狂Java联盟</a>
  6.  * <br/>Copyright (C), 2001-2016, Yeeku.H.Lee
  7.  * <br/>This program is protected by copyright laws.
  8.  * <br/>Program Name:
  9.  * <br/>Date:
  10.  * @author Yeeku.H.Lee kongyeeku@163.com
  11.  * @version 1.0
  12.  */
  13. public class IteratorErrorTest
  14. {
  15.     public static void main(String[] args)
  16.     {
  17.         // 创建集合、添加元素的代码与前一个程序相同
  18.         Collection books = new HashSet();
  19.         books.add("轻量级Java EE企业应用实战");
  20.         books.add("疯狂Java讲义");
  21.         books.add("疯狂Android讲义");
  22.         // 获取books集合对应的迭代器
  23.         Iterator it = books.iterator();
  24.         while(it.hasNext())
  25.         {
  26.             String book = (String)it.next();
  27.             System.out.println(book);
  28.             if (book.equals("疯狂Android讲义"))
  29.             {
  30.                 // 使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
  31.                 books.remove(book);
  32.             }
  33.         }
  34.         System.out.println(books);
  35.     }
  36. }

输出异常信息如下:

疯狂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() 方法的实现,源码如下:

  1. public boolean remove(Object o) {
  2.     return map.remove(o)==PRESENT;
  3. }

而 PRESENT 常量的定义如下:

private static final Object PRESENT = new Object();

为何集合 map 当其 remove 输出时的最后一个元素时,可以返回 true,而 remove 其它元素时,直接抛出异常?

有人解释道:在执行了 remove 方法之后,再去执行循环 iter.next() 的时候,报java.util.ConcurrentModificationException,如果 remove 的是最后一条,就不会再去执行 next() 操作了,所以不会报错。

其实,看其 AbstractList.java 源码,主要有如下方法:

  1. private void checkForComodification() {
  2.     if (this.modCount != l.modCount)
  3.         throw new ConcurrentModificationException();
  4. }

而在 next(),add(),remove(),size() 等方法中都会调用该验证方法,如果 if 中的判断成立,则立刻抛出异常,具体详细请参看源码。

从本质上解释原因:  

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来集合的单链索引表,当原来的集合数量发生变化时,这个索引表的内容不会同步改变,当索引指针往后移动的时候就找不到要迭代的对象,按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。  

所以 Iterator 在工作的时候是不允许被迭代的对象被改变的,但可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。 

共勉~

给我留言

留言无头像?