Java 删除 List 中某些特定值

在 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);
}
[A, B, C, E, F, E, D, E]

很明显,只删除了一个 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);
}
[A, B, C, E, F, E, E]

上面的方式能达到目的,但是性能会比较差,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));
}

该方法真正做到了一行代码解决需求,且其底层已经做了部分优化。

转载请注明出处:码谱记录 » Java 删除 List 中某些特定值
标签: