上周给新来的同学讲了个需求,将一个过滤集合中不符合条件的元素,讲到实现方法的时候,遍历方式中,同学尝试了普通for循环和增强性for循环,不过都不太靠谱。来看下例子:
普通for循环
package cn.com.test;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
//遍历删除不是C的元素
for (int i = 0; i <list.size() ; i++) {
if(!"c".equals(list.get(i))){
list.remove(list.get(i));
}
}
System.out.print(list);
}
}
打印结果:
[b, c, e]
结果那么奇怪,其实仔细一看就知道其中的问题在哪,list每次删除一个元素,list的长度都应缩短,而此时的i的大小是根据list的长度判断的,当i增长到3时,此时list还有3个元素,循环就停止了。来看图说话:
有没有解决办法呢?
package cn.com.test;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
list1.add("e");
for (int i = list1.size()-1; i >=0 ; i--) {
if(!"c".equals(list1.get(i))){
list1.remove(list1.get(i));
}
}
System.out.print(list1);
}
}
[c]
从结果来看是解决了问题,不过还是看着比较蹩脚。
增强for循环
package cn.com.test;
import java.util.ArrayList;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
for (String str:list) {
if(!"c".equals(str))
list.remove(str);
}
System.out.print(list);
}
}
结果报错了:ConcurrentModificationException
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 cn.com.test.Demo2.main(Demo2.java:14)
for循环是通过迭代器Iterator实现的,从报错的内容可以看出,在对ArrayList进行修改时抛出的错误。查询ArrayList源码可以看出Iterator是以内部类的方式实现的。hasNext方法是判断容器中是否还有元素未被访问过,删除元素调用的是list本身的remove方法而不是迭代器本身的remove方法,会导致执行next方法校验变量expectedModCount 和modCount时不一致,所以报错ConcurrentModificationException,这个方式被pass了
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
迭代器遍历
最稳妥的方法还是直接用迭代器遍历,使用迭代器自由的remove方法删除元素
package cn.com.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if(!"c".equals(next)){
iterator.remove();
}
}
System.out.print(list);
}
}
执行结果:
[c]
不过新同学在调试过程中又出现了意外,将如下代码注释掉后竟造成了死循环。
String next = iterator.next();
if(!"c".equals(next)){
iterator.remove();
}
不过通过分析迭代器源码就可以看出,hasnext方法只是用来判断是否还有元素未被扫描,真正移动元素的的方法是next,next元素会修改被扫描元素的个数,源码如上图。不过这只使用于单线程的环境,多线程环境就需要考虑加锁来处理了。
虽然整个程序虽然看似简单,但是对于不怎么关注实现原理的同学来说真会是一头雾水。也给我上了一课,写代码只是招式,原理层的才是口诀心法。