`
kingxss
  • 浏览: 969387 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

迭代中增删Java集合中对象的正确方法

阅读更多

看下面这个例子:

@Test
public void listRemoveTest() {
	String[] temp={"Jim","Jim","Amli","Amli","Masu","Lina"};
	List<String> names = new ArrayList<String>();
	
	for (int i = 0; i < temp.length; i++) {
		names.add(temp[i]);
	}

	for (int i = 0; i < names.size(); i++) {
		String name = names.get(i);
		if("Jim".equals(name) || "Amli".equals(name)) {
			names.remove(i);
		}
	}
	
	for (String name : names) {
		System.out.println(name);
	}
}

 我们的期望值是移除所有名为“Jim”或者“Amli”的对象:

Masu
Lina

 而事实上输出结果为:

Jim
Amli
Masu
Lina

 造成该结果是由于 移除列表中对象后 列表长多缩短而列表索引值 i 没有做相应的调整,可以将代码修改如下:

for (int i = 0; i < names.size();) {
	String name = names.get(i);
	if("Jim".equals(name) || "Amli".equals(name)) {
		names.remove(i);
		continue;
	} else {
		i++;
	}
}

 还可以用集合的迭代器来处理,代码如下:

 

//错误处理方式
for (String name : names) {
	if("Jim".equals(name) || "Amli".equals(name)) {
		names.remove(name);
	}
}
//正确处理方式
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
	String name = iterator.next();
	if("Jim".equals(name) || "Amli".equals(name)) {
		iterator.remove();
	}
}

 

 

上述的错误方式会造成java.util.ConcurrentModificationException。

 

同样的问题也存在于Map中。例如下面的代码:

@Test
public void removeTest() {
	Map<String, String> params = new HashMap<String, String>();
	params.put("STUDENT_A", "S.A");
	params.put("STUDENT_B", "S.B");
	params.put("TEACHER_A", "T.A");
	params.put("TEACHER_B", "T.B");
	params.put("TEST", "TEST");
	
	try {
		removeTeacherParameters(params);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

//错误的使用方式
private void removeTeacherParameters(Map<String, String> params) {	    
	for(String key:params.keySet()){
		if(key.equals("TEST")){
			params.remove(key);
		}
		if(key.indexOf("TEACHER_")==-1){
			params.remove(key);
		}
	}
}

这里会出现java.util.ConcurrentModificationException:

java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java)
at java.util.HashMap$KeyIterator.next(HashMap.java)

 

出现这个问题原因是由于 集合中的修改次数标量 和 集合的迭代器的期望修改次数标量 没有匹配上造成的具体可以看HashMap中的代码:

public V remove(Object key) {
	Entry<K,V> e = removeEntryForKey(key);
	return (e == null ? null : e.value);
}

final Entry<K,V> removeEntryForKey(Object key) {
	int hash = (key == null) ? 0 : hash(key.hashCode());
	int i = indexFor(hash, table.length);
	Entry<K,V> prev = table[i];
	Entry<K,V> e = prev;

	while (e != null) {
		Entry<K,V> next = e.next;
		Object k;
		if (e.hash == hash &&
			((k = e.key) == key || (key != null && key.equals(k)))) {
			modCount++; //只增加了modCount
			size--;
			if (prev == e)
				table[i] = next;
			else
				prev.next = next;
			e.recordRemoval(this);
			return e;
		}
		prev = e;
		e = next;
	}

	return e;
}

 

而AbstractHashedMap中的代码显示在进行next()操作的时候会检测modCount 是否和 expectedModCount相等:

public class AbstractHashedMap extends AbstractMap implements IterableMap {
	protected transient int modCount;
	
	protected static abstract class HashIterator implements Iterator {
		protected int expectedModCount;
		
		public boolean hasNext() {
			return (next != null);
		}

		protected HashEntry nextEntry() { 
		    //当expectedModCount和modCount不相等时,就抛出ConcurrentModificationException<br>                        
			if (parent.modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			}
			HashEntry newCurrent = next;
			if (newCurrent == null)  {
				throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY);
			}
			HashEntry[] data = parent.data;
			int i = hashIndex;
			HashEntry n = newCurrent.next;
			while (n == null && i > 0) {
				n = data[--i];
			}
			next = n;
			hashIndex = i;
			last = newCurrent;
			return newCurrent;
		}
	}
	
	protected static class HashMapIterator extends HashIterator implements MapIterator {
		public Object next() {
			return super.nextEntry().getKey();
		}
	}
}

 

而正确的使用方法如下:

@Test
public void removeTest() {
	Map<String, String> params = new HashMap<String, String>();
	params.put("STUDENT_A", "S.A");
	params.put("STUDENT_B", "S.B");
	params.put("TEACHER_A", "T.A");
	params.put("TEACHER_B", "T.B");
	params.put("TEST", "TEST");
	
	try {
		removeStudentParameters(params);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

//正确的使用方式
private void removeStudentParameters(Map<String, String> params) {
	Iterator<Entry<String, String>> it = params.entrySet().iterator();
	while (it.hasNext()) {
		String key = it.next().getKey();
		if(key.equals("TEST") || key.indexOf("STUDENT_")==-1){
			it.remove();
		}
	}
}

 

这是由于在迭代器在进行remove操作时候, 同步了modCount 和 expectedModCount的值:

public class AbstractHashedMap extends AbstractMap implements IterableMap {
	protected transient int modCount;
	
	protected static abstract class HashIterator implements Iterator {
		protected int expectedModCount;

		public void remove() {
			if (last == null) {
				throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID);
			}
			if (parent.modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			}
			parent.remove(last.getKey());
			last = null;                   
			//重新设置了expectedModCount的值,避免了ConcurrentModificationException的产生                       
			expectedModCount = parent.modCount;
		}
	}
}

 产生ConcurrentModificationException的原因就是:

    执行remove(Object o)方法之后,modCount和expectedModCount不相等了。然后当代码执行到next()方法时,判断了checkForComodification(),发现两个数值不等,就抛出了该Exception。要避免这个Exception,就应该使用结合内置迭代器的remove()方法。同理,在进行集合迭代的时候往集合中插入新的元素也会造成同样的问题,要解决这个问题是新建一个集合来处理。

 

参考:

http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap

http://stackoverflow.com/questions/16070070/why-does-list-removeint-throw-java-lang-unsupportedoperationexception

http://www.blogjava.net/evanliu/archive/2008/08/31/224453.html

 

分享到:
评论

相关推荐

    AIC的Java课程1-6章

    第3版 机械工业出版社  教学内容和要求 知识点 重要程度 使用频度 难度 Java 入门 高 中 易 变量和运算符 高 高 中 控制结构 高 高 易 数组 高 高 中 方法 很高 高 中 封装 很高 很高 难...

    java 面试题 总结

    子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。...

    超级有影响力霸气的Java面试题大全文档

    子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。...

    ArrayList.java

    官方翻译:大小可变数组实现List接口的。 实现了所有可选列表操作,并允许所有元素,包括null。 除了实现List接口,此类提供方法来操作在内部用于存储列表中的阵列的大小。...这个类是成员的Java集合框架

    突破程序员基本功的16课.part2

    10.2.6 Java集合中的队列 10.3 双向队列 10.4 小结 第11课 树和二叉树 11.1 树的概述 11.1.1 树的定义和基本术语 11.1.2 树的基本操作 11.1.3 父节点表示法 11.1.4 子节点链表示法 11.2 二叉树 11.2.1 ...

    Guava 16.0 API (CHM格式)

    Guava 是一个 Google 的基于java1.6的类库集合的扩展项目,包括 collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, 等等. 这些高质量的 API 可以使你...

    算法导论(part1)

    ·在第21.4节中,我们换掉了对不相交-集合-并(disjoint-set-union)数据结构运行时间的证明,代之以利用潜势方法(potential method)导出一个紧致界的证明。 ·在第22.5节中,对强连通子图算法正确性的证明更简单、...

    asp.net知识库

    动态调用对象的属性和方法——性能和灵活性兼备的方法 消除由try/catch语句带来的warning 微软的应试题完整版(附答案) 一个时间转换的问题,顺便谈谈搜索技巧 .net中的正则表达式使用高级技巧 (一) C#静态成员和...

    算法导论(part2)

    ·在第21.4节中,我们换掉了对不相交-集合-并(disjoint-set-union)数据结构运行时间的证明,代之以利用潜势方法(potential method)导出一个紧致界的证明。 ·在第22.5节中,对强连通子图算法正确性的证明更简单、...

    Spring-Boot-Practice-W-David

    Spring启动实践戴维 Sprint 1 原型快速原型 原型页面: 全部: 查看类别标签对所有数据进行硬编码,并... 如果我在模型中有一个带有getId()方法的名为foo的对象,并且想创建一个具有id的查询参数,则可以按以下方式

    freemarker总结

    上面的语法格式中,sequence就是一个集合对象,也可以是一个表达式,但该表达式将返回一个集合对象,而item是一个任意的名字,就是被迭代输出的集合元素.此外,迭代集合对象时,还包含两个特殊的循环变量: item_index:...

    Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码

    4.3.2 集合运算中的空值行为 110 4.3.3 空值与GROUP BY和ORDER BY 112 4.3.4 空值与聚合函数 114 4.4 小结 114 第5章 关于问题 116 5.1 问出好的问题 116 5.2 提问的目的 117 5.3 问题的种类 117 5.4 关于...

Global site tag (gtag.js) - Google Analytics