0.需求
从List列表中删除删除一个元素
#JDK版本
java version "1.8.0_241"
1.现象
public class ListTest {
List<String> list = new ArrayList<>();
@Before
public void before(){
//初始化数据
for (int i = 0; i < 6; i++) {
list.add(String.valueOf(i));
}
//打印
list.forEach(t->{
System.out.println("val:" + t);
});
}
@Test
public void remove(){
//删除元素"2"
list.forEach(t -> {
if("2".equals(t)){
list.remove(t);
}
});
//打印
list.forEach(t->{
System.out.println(t);
});
}
}
运行后,发生如下异常:
java.util.ConcurrentModificationException
at java.util.ArrayList.forEach(ArrayList.java:1260)
at cn.pbdata.issue.manual.simple.ListTest.remove(ListTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
从异常信息可以发现,异常发生在java.util.ArrayList.forEach(ArrayList.java:1260)方法中。
查看ArrayList的源码:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
//第1260行
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
其中,
modCount是ArrayList中的一个成员变量。从其中的注释说明中可以看到modCount表示对List的修改次数,每次调用add()方法或者remove()方法,就会对modCount进行加1操作。
在此例中,list最初有6个元素,那么最初的modCount=6,
我们进行了remove一次,所以 modCount=7,
而expectedModCount还是为6,导致modCount != expectedModCount,所以就出现了并发异常。
解决方案
如果要在List中删除元素,可以有以下几种办法:
1. 使用迭代器
我们重构之前的测试代码,使用 Iterator进行删除操作。在 Iterator迭代器中,可以使用remove()。测试代码如下:
@Test
public void removeV2(){
//删除元素"2"
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String t = iterator.next();
if("2".equals(t)){
iterator.remove();
}
}
//打印
list.forEach(t->{
System.out.println(t);
});
}
测试通过,运行正常。
这是因为迭代器在循环过程中调用是安全的,remove()方法就不会导致ConcurrentModificationException。
2. 迭代期间不删除
如果一定要使用for-each循环,那么我们可以再构建一个list,保存需要删除的元素,等到迭代结束后,再移除元素。测试代码如下:
@Test
public void removeV3(){
//删除元素"2"
List<String> toDelList = new ArrayList();
list.forEach(t -> {
if("2".equals(t)){
toDelList.add(t);
}
});
list.removeAll(toDelList);
//打印
list.forEach(t->{
System.out.println(t);
});
}
这也是解决问题的一种方法。
3. 使用removeIf()
从Java 8开始,Collection接口引入了removeIf()方法。我们可以使用函数式编程的方法进行处理。测试代码如下:
@Test
public void removeV4(){
//删除元素"2"
list.removeIf(t -> "2".equals(t));
//打印
list.forEach(t->{
System.out.println(t);
});
}
4. 使用函数式编程
还可以使用函数式编程中的流处理进行删除。测试代码如下:
@Test
public void removeV5(){
//删除元素"2"
List<String> afterList = list.stream()
.filter(t -> (!"2".equals(t)))
.map(Object::toString)
.collect(Collectors.toList());
//打印
afterList.forEach(t->{
System.out.println(t);
});
}
end!!!