从循环内的 List 中删除项目
在循环内从列表中删除项目很棘手,这是因为列表的索引和长度发生了变化。
给出以下列表,这里有一些示例会给出意想不到的结果,有些会给出正确的结果。
List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Strawberry");
不正确
删除 for
语句的迭代跳过香蕉:
代码示例只会打印 Apple
和 Strawberry
。Banana
被跳过,因为一旦 Apple
被删除它会移动到索引 0
,但同时 i
会增加到 1
。
for (int i = 0; i < fruits.size(); i++) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
}
}
在增强的 for
语句中删除引发异常:
因为迭代收集并同时修改它。
抛出:java.util.ConcurrentModificationException
for (String fruit : fruits) {
System.out.println(fruit);
if ("Apple".equals(fruit)) {
fruits.remove(fruit);
}
}
正确
使用 Iterator
在 while 循环中删除
Iterator<String> fruitIterator = fruits.iterator();
while(fruitIterator.hasNext()) {
String fruit = fruitIterator.next();
System.out.println(fruit);
if ("Apple".equals(fruit)) {
fruitIterator.remove();
}
}
Iterator
接口有一个 remove()
方法,仅适用于这种情况。但是,此方法在文档中标记为可选 ,它可能会抛出一个 UnsupportedOperationException
。
抛出:UnsupportedOperationException - 如果此迭代器不支持 remove 操作
因此,建议检查文档以确保支持此操作(实际上,除非集合是通过第三方库获得的不可变集合或使用 Collections.unmodifiable...()
方法之一,否则几乎总是支持该操作)。
当使用 Iterator
时,当 List
的 modCount
从创建 Iterator
时改变时,会抛出 ConcurrentModificationException
。这可能发生在同一个线程或共享相同列表的多线程应用程序中。
modCount
是一个 int
变量,它计算此列表在结构上被修改的次数。结构变化实质上意味着在 Collection
对象上调用 add()
或 remove()
操作(Iterator
所做的更改不计算在内)。创建 Iterator
时,它会存储此 modCount
,并在 List
的每次迭代中检查当前 modCount
是否与创建 Iterator
时相同。如果 modCount
值有变化,它会抛出一个 ConcurrentModificationException
。
因此,对于上面声明的列表,下面的操作不会抛出任何异常:
Iterator<String> fruitIterator = fruits.iterator();
fruits.set(0, "Watermelon");
while(fruitIterator.hasNext()){
System.out.println(fruitIterator.next());
}
但是在初始化 Iterator
之后向 List
添加一个新元素会抛出一个 ConcurrentModificationException
:
Iterator<String> fruitIterator = fruits.iterator();
fruits.add("Watermelon");
while(fruitIterator.hasNext()){
System.out.println(fruitIterator.next()); //ConcurrentModificationException here
}
向后迭代
for (int i = (fruits.size() - 1); i >=0; i--) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
}
}
这不会跳过任何事情。这种方法的缺点是输出是反向的。但是,在大多数情况下,你删除无关紧要的项目。你永远不应该用 LinkedList
这样做。
向前迭代,调整循环索引
for (int i = 0; i < fruits.size(); i++) {
System.out.println (fruits.get(i));
if ("Apple".equals(fruits.get(i))) {
fruits.remove(i);
i--;
}
}
这不会跳过任何事情。当从 List
中移除 i
th 元素时,最初位于 index i+1
的元素成为新的 i
th 元素。因此,循环可以减少 i
,以便下一次迭代处理下一个元素,而不会跳过。
使用应该删除列表
ArrayList shouldBeRemoved = new ArrayList();
for (String str : currentArrayList) {
if (condition) {
shouldBeRemoved.add(str);
}
}
currentArrayList.removeAll(shouldBeRemoved);
此解决方案使开发人员能够以更清洁的方式检查是否删除了正确的元素。
Version => Java SE 8
在 Java 8 中,以下替代方案是可能的。如果不必在循环中进行移除,则这些更干净且更直接。
过滤流
List
可以流式传输和过滤。可以使用适当的过滤器来移除所有不需要的元素。
List<String> filteredList =
fruits.stream().filter(p -> !"Apple".equals(p)).collect(Collectors.toList());
请注意,与此处的所有其他示例不同,此示例生成一个新的 List
实例并保持原始 List
不变。
使用 removeIf
如果只需要删除一组项目,则可以节省构建流的开销。
fruits.removeIf(p -> "Apple".equals(p));