- 问题及分析
- 正确删除集合元素
问题及分析
注:在浏览阿里巴巴Java开发手册时,自己测试Java遍历集合并删除元素时发现有些巧合以及总结
先写开发手册里一个例子,大家猜一下以下代码的输出
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
System.out.println("list original size is " + list.size());
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println("list size is " + list.size());
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
System.out.println("list original size is " + list.size());
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println("list size is " + list.size());
上面两个例子的输出结果是什么?意外吗?
1,2可以成功;1, 2, 3删除抛出异常
- 第一个:
list original size is 2
list size is 1 - 第二个:
list original size is 3
java.util.ConcurrentModificationException
我们可以看一下此处代码的反编译代码:
List<String> list = new ArrayList();
list.add("1");
list.add("2");
// list.add("3");
System.out.println("list original size is " + list.size());
Iterator var2 = list.iterator();
while(var2.hasNext()) {
String item = (String)var2.next();
if ("1".equals(item)) {
list.remove(item);
}
}
System.out.println("list size is " + list.size());
通过查看反编译代码,发现我们使用的foreach遍历仍然是迭代器Iterator遍历,而ArrayList中Iterator源码:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
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];
}
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();
}
}
......
}
modCount是集合添加,删除等改变集合结构的次数(改变集合大小),expectedModCount是预期的变化次数;
分析一下:
[“1”, “2”] : 当"1"被遍历删除后,游标cursor的值从0变为1,集合长度也变为1,这是hasNext返回false,比较表示没有下一个元素,结束遍历;
[“1”, “2”, “3”] : 如上,当"1"遍历删除后,游标cursor从0变为1,集合长度变为2,hasNext返回true,执行remove时,checkForComodification()方法验证是否同时修改,此方法表modCount != expectedModCount,modCount是3,expectedModCount也是3,而在删除后modCount变为4,而hasNext()方法不返回false,next()方法调用时就会抛出异常;
注:由其游标变化规律可以看出,如果hasNext提前结束,不执行后面的next取数据,就可以删除集合元素,故可以删除集合中倒数第二个元素而不抛出异常(实践也如此)
正确删除集合元素
- 迭代器方式
public void positiveForEachTest() {
List<String> list = new ArrayList<>();
list.add("w");
list.add("li");
list.add("z");
System.out.println("list original size is " + list.size());
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("li".equals(item)) {
iterator.remove();
}
}
System.out.println("after list remove elem `li`, it's size is " + list.size());
}