优雅的删除List元素

在工作中的许多场景下,我们都会使用到List这个数据结构,那么同样的有很多场景下需要删除List中的某一个元素或某几个元素,那么我们该如何正确无误地删除List中的元素

知识准备

for循环的执行顺序

这里借用百度百科的一张图,简明扼要的介绍一下。

Iterator迭代器介绍

迭代器:迭代其实我们可以简单地理解为遍历,是一个标准化遍历各类容器里面的所有对象的方法类,它是一个很典型的设计模式。Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向每次遍历前都需要知道要遍历集合的内部结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 遍历list
List list = new ArrayList();
list.add(1);
list.add(2);
for (int i = 0; i < list.size(); i++) {
Object object = list.get(i);
// do something
}

// 遍历map
Map<String,String> map = new HashMap<>();
map.put("1","first");
map.put("2","second");
for (Map.Entry<String,String> entry : map.entrySet()){
String key = entry.getKey();
String value = entry.getValue();
// do something
}

对于这两种方式,我们总是都事先知道集合的内部结构,访问代码和集合本身是紧密耦合的,无法将访问逻辑从集合类和遍历方法中分离出来。同时每一种集合对应一种遍历方法,代码无法复用。
为了解决以上问题, Iterator 模式腾空出世,它总是用同一种逻辑来遍历集合。使得需要遍历集合的人,在遍历的时候不需要了解集合的内部结构,所有的内部状态都由 Iterator 来维护。遍历集合的方法不直接和集合类打交道,它总是控制 Iterator,向它发送”向前”,”向后”,”取当前元素”的命令,就可以间接遍历整个集合。

错误:for循环顺序遍历

直接使用简单for循环,以for (int i = 0; i < list.size(); i++) 进行遍历,这种方式可能会在遍历的过程中漏掉部分元素,从而出现少删的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 通过简单的遍历方式,在遍历的过程中有可能会漏掉元素
* 取第二个元素i=1时,满足条件被删掉,原有的数组的第三个元素,变成了新数组的第二个元素
* i++后i=2,但i=2指向的是新数组中的第三个元素,那么原数组中的第三个元素就被漏掉了
*
* @param list
* @param element
* @return
*/
public static List forRemove(List list, Object element) {
for (int i = 0; i < list.size(); i++) {
if (element.equals(list.get(i))) {
list.remove(i);
}
}
return list;
}

使用增强for循环是,如果删除后继续向下循环则会报java.util.ConcurrentModificationException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 使用增强for循环是,如果删除后继续向下循环则会报
* java.util.ConcurrentModificationException
*
* @param list
* @param element
* @return
*/
public static List forceForRemove(List list, Object element) {
for (Object item : list) {
if (item.equals(element)) {
list.remove(item);
}
}
return list;
}

异常如下:

1
2
3
4
5
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.lingyejun.leetcode.RemoveListElement.forceForRemove(RemoveListElement.java:57)
at com.lingyejun.leetcode.RemoveListElement.main(RemoveListElement.java:112)

我们使用逆向遍历的方式可以得到正确的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 逆向循环,是正确的
* 1-->2-->3-->4
* 逆向循环时,倒数第一个元素满足条件被删除时,i--后,原数组的倒数第二个变成了新数组的倒数第一个元素
* i = size-2指向新数组的最后一个元素,没有漏掉。
* 同理倒数第二个元素满足条件被删除时,i--后,原数组的倒数第三个变成了新数组的倒数第二个元素
* i= size-3指向新数组的倒数第二个元素,也没有漏掉
*
* @param list
* @param element
* @return
*/
public static List reverseorRemove(List list, Object element) {
for (int i = list.size() - 1; i >= 0; i--) {
if (element.equals(list.get(i))) {
list.remove(i);
}
}
return list;
}

使用增强for循环,删除元素后,立即跳出,则正常退出,但缺点是不能向后继续循环了。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 删除元素后,立即跳出,则正常退出,但不能向后继续循环了
*
* @param list
* @param element
* @return
*/
public static List forceForRemove1(List list, Object element) {
for (Object item : list) {
if (item.equals(element)) {
// 删除后立马终端循环,会正常跳出,但代价是不能继续向后循环了
list.remove(item);
break;
}
}
return list;
}

优雅删除:使用Iterator迭代器

使用迭代器可,正确无误的删除,代码简洁优雅,推荐使用!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 使用迭代器可,正确无误的删除
*
* @param list
* @param element
* @return
*/
public static List iteratorRemove(List list, Object element) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object cur = iterator.next();
if (cur.equals(element)) {
// 注意!!!这里时Iterator.remove()!!!而不是list.remove()!!!
iterator.remove();
}
}
return list;
}

优雅删除:使用Steam流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
List<Map<String, Object>> mapList = new ArrayList<>();

Map<String, Object> map1 = new HashMap<>();
map1.put("id", 1);
map1.put("name", "Alice");
mapList.add(map1);

Map<String, Object> map2 = new HashMap<>();
map2.put("id", 2);
map2.put("name", "Bob");
mapList.add(map2);

Map<String, Object> map3 = new HashMap<>();
map3.put("id", 3);
map3.put("name", "Charlie");
mapList.add(map3);

int idToRemove = 2;

List<Map<String, Object>> updatedMapList = mapList.stream()
.filter(map -> !map.get("id").equals(idToRemove))
.collect(Collectors.toList());

System.out.println(updatedMapList);
}
}

在这个示例中,我们有一个 mapList,其中包含了多个 Map 元素。我们希望从中删除 id 为 2 的 Map 元素。我们使用 Stream 的 filter 操作来创建一个新的流,其中不包含要删除的元素。最后,通过 collect 操作将过滤后的 Map 元素收集到一个新的 List 中。

同样地,这种方法不会改变原始的 mapList,而是创建了一个新的 List 包含了需要保留的 Map 元素。如果需要更新原始 List,只需将 updatedMapList 赋值给 mapList 即可。

参考文章:

https://jingyan.baidu.com/article/7f766dafaa6ee04101e1d0e6.html

http://wiki.jikexueyuan.com/project/java-enhancement/java-thirty.html