不要在 foreach 循环里进行元素的 remove/add 操作
1、remove 元素请使用 Iterator方式,如果并发操作,需要对Iterator对象加锁
// A code block
List<String> a = new ArrayList<String>();
a.add("1");
a.add("2");
for (String temp : a) {
if ("1".equals(temp)) {
a.remove(temp);
}
}
说明:如果将if (“1”.equals(temp)) { 里面的 1 换成 2的话,会报下面这个错误
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.song.pojo.Test01.test01(Test01.java:28)
at com.song.pojo.Test01.main(Test01.java:17)
**原因:**所以,之所以会抛出ConcurrentModificationException异常,是因为代码中使用了foreach循环,而在foreach循环中,集合遍历是通过iterator进行的,但是元素的add/remove却是直接使用集合类自己的方法,这就导致Iterator在遍历的时候会发现有一个元素在自己不知不觉的情况下被添加/删除了,就会抛出一个异常,用来提示用户可能发生了并发修改。
2、解决办法?
2-1、直接使用普通for循环 遍历
因为普通for循环并没有使用Iterator的遍历,所以压根就没有进行fail-fast的检验
2-2、直接使用Iterator 遍历
如何直接使用iterator提供的add/remove方法,那么就可以修改到expectedModCount的值,那就不会再抛出这个异常了,其实现的代码如下:
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2-3、使用Java 8中提供的filter过滤:
Java 8中可以把集合转换成流,对于流有一种filter操作,可以对原始Stream进行某项测试,通过测试的元素被留下来生成一个新的流:
public static void main(String[] args) {
List<String> userNames = new ArrayList<String>();
userNames.add("Tom");
userNames.add("Jack");
userNames.add("Mic");
userNames.add("Seven");
userNames = userNames.stream().filter(userName -> !userName.equalsIgnoreCase("Jack")).collect(Collectors.toList());
System.out.println(userNames);
}
2-4、直接使用fail-safe的集合类:
在Java中,除了一些普通的集合类以外,还有一些采用了fail-safe机制的集合类,这样的集合容器在遍历时不时直接在集合内容*问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所做的修改并不能被迭代器检测到,所以不会抛出ConcurrentModificationException。
public static void main(String[] args) {
ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>();
userNames.add("Tom");
userNames.add("Jack");
userNames.add("Mic");
userNames.add("Seven");
for (String userName : userNames) {
if (userName.equals("Jack")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
}
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
以上几种方式都可以避免触发fail-fast机制,避免抛出异常。如果是并发场景,建议使用concurrent包中的容器;如果是单线程场景,Java8之前的代码中建议使用Iterator进行元素删除/添加;Java8之后可以考虑使用Stream及filter.