如何从集合中移除元素?
有一个水果名称集合,存储了apple,banana,orange三个水果名称,现要求移除其中的apple元素,同学给出了如下答案,这个答案正确吗?如果不正确,要如何修改?
public static void removeDataFromList() {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("orange");
list.add("banana");
//移除集合中的元素apple
for (String str : list) {
if (str.equals("apple")) {
list.remove("apple");
}
}
System.out.println(list.size());
}
该方法在执行会报java.util.ConcurrentModificationException
下面详细分析下这个异常出现的原因:
当执行list.add()方法时,观察源码调用的是这个方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
再查看这个扩充容量的方法ensureCapacityInternal:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里有个很重要的属性 modCount,这个属性是从父类AbstractList继承而来,
当我们对数组进行添加或删除时,该值都会加1。删除方法最终还是调用的如下方法:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null;
可以看到modCount次数也是增加了一次的。
由于 for (String str : list) 这个方法本质上是调用的ArrayList中的迭代器来遍历的,源码为
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
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];
}
可以看到 ,当执行next()方法时,会首先调用checkForComodification这个方法,
该方法实现为:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
由于遍历的时候就构造了 Itr对象,并且把modCount的值赋给了expectedModCount,
而当我们执行list.remove()方法时,只改变了modCount的值,没有改变expectedModCount的值,
最终二者不相等,就会抛出ConcurrentModificationException。
那么如何解决这个问题呢?
有几种方式
方法一,迭代器模式遍历数组:
public static void removeDataMethod1() {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("orange");
list.add("banana");
//移除集合中的元素apple
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String next = iterator.next();
if (next.equals("apple")) {
iterator.remove();
}
}
System.out.println(list.size());
}
方法二,利用lambda表达式,但是要求jdk8:
public static void removeDataMethod2() {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("orange");
list.add("banana");
//移除集合中的元素apple
list.removeIf(next -> next.equals("apple"));
System.out.println(list.size());
}
方法三,利用游标遍历的方式,
但是要注意,只能从最后面往最前面遍历,具体原因各位可以思考一哈:
public static void removeDataMethod3() {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("orange");
list.add("banana");
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).equals("apple")) {
list.remove(list.get(i));
}
}
System.out.println(list.size());
}
如有疑问,可以在下方留言讨论或者私聊,大家可以一起交流