问题
ArrayList作为Java程序中最常被使用的集合类型,使用迭代方式遍历该集合,并删除其中的某些特定元素是一个很常见的操作场景。我们直接看来示例代码:
public class TestForeach{
public static void main(String args[]) {
List list = new ArrayList();
list.add("大锤");
list.add("二锤");
for (String s : list) {
if (s.equals("二锤")) {
list.remove(s);
}
}
}
}
此代码编译正确,运行时却会抛出如下的错误信息:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at arraylist.AddRemoveListElement.main(AddRemoveListElement.java:17)
解决方案一
我们可以使用 Iterator遍历 代替 for循环 ,从而解决此问题。在 Iterator 遍历的过程中,允许使用 remove()方式去删除底层支撑的集合中的元素。
List list = new ArrayList();
list.add("大锤");
list.add("二锤");
Iterator iter = list.iterator();
while(iter.hasNext()){
String str = iter.next();
if( str.equals("二锤") )
{
iter.remove();
}
}
此方案的背后的逻辑比较复杂,我总结出以下几个要点,希望可以帮助理解:
1. for循环只是Java的语法糖,底层还是会使用 Iterator 方式。
2. ArrayList的实现细节中,有一个记录ArrayList底层数组修改次数的计数器
3. ArrayList的remove方法会修改计数器的值
4. Iterator的remove方法会修正计数器的值
5. Iterator 迭代的过程会检查计数器的值
解决方案二
如果不使用 ArrayList, 而是使用 CopyOnWriteArrayList, 那么一样可以解决此错误。CopyOnWriteArrayList 是ArrayList的一个线程安全的变种,它的每次修改自身的操作(add, set, remove)都会在拷贝底层数组,从而获得一个全新的数组。
public static void main(String args[]) {
List list = new CopyOnWriteArrayList();
list.add("大锤");
list.add("二锤");
for (String s : list) {
if (s.equals("二锤")) {
list.remove(s);
}
}
}
当然了,CopyOnWriteArrayList 由于每次修改操作都需要拷贝获取新的底层数组,性能上会低于ArrayList。
思维扩展
前面,我们演示了ArrayList的问题和两个解决方案,那么同为JDK中LinkedList和HashSet等其他的Collection类似是否也存在相同的问题呢?我们来看两个实例代码:
public static void main(String args[]) {
Set<String> set = new HashSet();
set.add("大锤");
set.add("二锤");
for (String s : set) {
if (s.equals("二锤")) {
set.remove(s);
}
System.out.println(s);
}
}
//输出结果 :二锤大锤
public static void main(String args[]) {
LinkedList<String> llist = new LinkedList();
llist.add("大锤");
llist.add("二锤");
for (String s : llist) {
if (s.equals("二锤")) {
llist.remove(s);
}
System.out.println(s);
}
}
//输出结果: 大锤二锤(s.equals("大锤")时只会输出大锤)
建议大家上机验证,这两个实例代码的运行结果与具体的 JDK 的版本有关。具体而言,需要参考 ArrayList、HashSet 和 LinkedList 这三个类的内部迭代器 Iterator 类的 hasNext() 方法的实现细节。