迭代中增删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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值