在 Java 中,使用 List 的 remove()
方法从List中删除特定值很简单。但是,我们需要知道,List 是可以存放重复数据的,要想删除满足条件的所有值会有一点难度。
数据准备
为了更好地说明需要达到的目的,这里给出一个数据样例:
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D", "E", "F", "E", "D", "E"));
当指定删除的数据为 “D”,应该留下的数据为:
["A", "B", "C", "E", "F", "E", "E"]
遍历删除
我们已经知道,List 本身有 remove() 方法,它接收一个参数:需要删除的内容/需要删除的索引。
public static void removeOneElement(List<String> list){
String removed = "D";
list.remove(removed);
System.out.println(list);
}
很明显,只删除了一个 D。
for-each
要想删除所有的 D,我们可以用循环列表的方式:
public static void removeElement(List<String> list){
String removed = "D";
for(String str : list){
if(str.equals(removed)){
list.remove(str);
}
}
}
这次,结果没有出现,反而得到了一个并发修改异常 ConcurrentModificationException。
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at com.mapull.list.RemoveValues.removeElement(RemoveValues.java:27)
at com.mapull.list.RemoveValues.main(RemoveValues.java:16)
因为我们在遍历 List 的过程中,程序需要知道遍历多少次,其内部维护了一个 count 整数值,当我们调用 remove() 方法时,会导致该值发生变化。为了保证能遍历完整个 list ,程序中有限制——禁止遍历时修改内部元素数量,否则就会出现异常 ConcurrentModificationException。
while
既然知道了 for-each 需要知道遍历次数,那换一个遍历方式,用 while() 循环就不需要管遍历次数了:
public static void removeElementWhile(List<String> list){
String removed = "D";
while(list.contains(removed)){
list.remove(removed);
}
System.out.println(list);
}
上面的方式能达到目的,但是性能会比较差,remove() 方法每次只删除一个满足条件的数据,元素每一次的移动都会导致 ArrAYList 重新生成一次,数组的索引也要重新标注。ArrayList 的本质是一个数组,而 Java 中数组的长度是不可变的,每次从 List 中删除一个元素,都会使得所有元素重新复制到一个新的数组中。
Iterator
Iterator 可以跟踪 List 的状态,它可以在 list 被删除元素后,维护好删除后列表的数量,不至于出现并发修改异常。
public static void removeElementIte(List<String> list){
String removed = "D";
for(Iterator<String> it = list.iterator(); it.hasNext();){
String value = it.next();
if(value.equals(removed)){
it.remove();
}
}
}
需要注意的是,这里使用的迭代器自己的 it.remove() 方法,无参数的。而不是 List 有参数的 remove() 方法。
临时变量法
上面的方法都是在现有的 List 中去掉不想要的数据,我们还可以换个思路。将符合要求的数据放到临时变量中:
public static void remainElement(List<String> list){
String removed = "D";
List<String> results = new ArrayList<>();
for(String str : list){
if(!str.equals(removed)){
results.add(str);
}
}
}
我们在没有修改原 List 的情况下,得到了一个新的满足条件的 List。这是一点典型的用空间换时间的例子,我们通过临时变量的方式,避免了 List 的多次复制。
当然,你可能会觉得上面的代码并不满足需要,因为我们需要的场景是,将 List 中特定的数据删除掉,返回个新的 List 是啥意思。
下面,我们清除原始 List 并将新得到的临时 List 添加到其中:
public static void remainElement(List<String> list){
String removed = "D";
List<String> results = new ArrayList<>();
for(String str : list){
if(!str.equals(removed)){
results.add(str);
}
}
list.clear();
list.addAll(remainingElements);
}
stream 流
Java 8 中引入的 Stream 流可以让代码变得简洁易懂:
public static void rmElementFilter(List<String> list){
String removed = "D";
List<String> results = list.stream()
.filter(s -> !s.equals(removed))
.collect(Collectors.toList());
}
关键代码只有一行了,当然同样是生成了新的元素,要是想修改原 List,可以参照上面的做法。
removeIf
Java 8 中对部分方法进行了扩充,在 List 中添加了 removeIf() 方法。
public static void rmElementRemoveIf(List<String> list){
String removed = "D";
list.removeIf( s -> s.equals(removed));
}
该方法真正做到了一行代码解决需求,且其底层已经做了部分优化。