如果要进行remove操作,可以调用迭代器的 remove 方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身remove/add方法,迭代器都将抛出一个ConcurrentModificationException,这就是单线程状态下产生的 fail-fast 机制。
正例
public class ModList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if("2".equals(next)){
iterator.remove();
}
}
for (String item : list) {
System.out.println(item);
}
}
}
反例
public class ModList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String s : list) {
System.out.println(s);
if("2".equals(s)){
list.remove(s);
}
}
for (String item : list) {
System.out.println(item);
}
}
}
有一个特殊的情况,就是当list有两个元素时,当我们采用这种方式移除第一个时,方法是能够成功运行的,原因是什么呢?
- 通过反编译工具分析上述代码:可以发现foreach遍历列表时,底层也是采用的iterator的方式。
- 将上述代码等价于下面的形式:
public class ModList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String next = iterator.next();
if("1".equals(next)){
list.remove(next);
}
}
for (String item : list) {
System.out.println(item);
}
}
}
- 分析迭代器遍历的流程,该流程涉及的函数调用有hasNext()、next(),由于next()函数是需要检查list是否被修改的,因此可以发现的一点是该方法没有被调用,查看hasNext()函数源码发现,当我们删除第一个元素时,该函数将返回false,因此接下来的代码将不会被执行。hasNext()函数源码:
public boolean hasNext() {
// 删除第一个之后,cursor等于size,所以这里直接返回了false
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}