在平常开发中,我们会经常碰到需要从一个list集合中,筛选过滤出符合条件的元素。或者说循环遍历list,删除掉不符合条件的元素。
举个例子:现有一个list,需要删除所有“李”姓的人名。
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
小贴士:这里为什么不直接用Arrays.asList返回的值而初始化了一个新的ArrayList?
具体原因我们可以看一下Arrays.asList的源码
public static <T> List<T> asList(T... a) {
return new Arrays.ArrayList<>(a);
}
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable{}
可以看出,虽然Arrays.asList返回的类也叫ArrayList,但是这是Arrays自己定义的ArrayList,他并没有实现remove方法,故在删除元素是会报异常。
public E remove(int index) {
throw new UnsupportedOperationException();
}
书归正传,回到样例,我们先看看最佳实现的写法,也就是推荐的写法:
1、使用Iterator
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.startsWith("李")) {
iterator.remove();
}
}
System.out.println(list);
2、使用removeIf
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
list.removeIf(next -> next.startsWith("李"));
System.out.println(list);
使用了Lambda的写法,通过查看removeIf源码即可发现,实际原理其实与1一致。
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
3、stream流+filter
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
list = list.stream().filter(e -> !e.startsWith("李")).collect(Collectors.toList());
System.out.println(list);
与1、2的区别为,方法3相当于是从原始list中挑选出符合条件的元素添加到一个新的list,也就是说过滤前和过滤后的list对象不是同一个,结果值新建了一个list对象。
不推荐的写法,或者说这种写法可能还会出现错误
4、for循环删除(结果错误)
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.startsWith("李")) {
list.remove(s);
}
}
System.out.println(list);
这种写法可以发现,结果是错误的,原因是在for循环中判断结束的条件为list.size(),而随着删除元素,这个值是变化的,也就是会提前结束循环,并不能遍历到全部元素。
我们可以通过增加一句i—来解决这个问题:
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
if (s.startsWith("李")) {
list.remove(s);
i--;
}
}
System.out.println(list);
或者使用倒序遍历:
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
for (int i = list.size() - 1; i >= 0; i--) {
String s = list.get(i);
if (s.startsWith("李")) {
list.remove(s);
}
}
System.out.println(list);
5、增强for循环删除(抛异常)
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
for (String s : list) {
if (s.startsWith("李")) {
list.remove(s);
}
}
System.out.println(list);
会抛出java.util.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)
从异常可以看出,这种方式使用的还是迭代器循环,只是在删除时使用的是list的remove方法而不是迭代器的remove方法,相当于:
List<String> list = new ArrayList<>(Arrays.asList("张三", "李四", "周一", "刘四", "李强", "李白"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.startsWith("李")) {
list.remove(next);
}
}
System.out.println(list);
而异常的报错点就在于iterator.next();方法中,该异常为集合操作中很常见的异常之一,即并发修改异常。
迭代器iterator在取下个元素的时候都会去判断要修改的数量(modCount)和期待修改的数量(expectedModCount)是否一致,不一致则会报错,而 ArrayList 中的 remove 方法并没有同步期待修改的数量(expectedModCount)值,所以会抛异常了。