在Java中调用foreach循环中的remove

在Java中,当使用foreach循环遍历集合时,对集合调用remove是否合法?例如:

List<String> names = ....
for (String name : names) {
// Do something
names.remove(name).
}

作为附录,移除尚未迭代的项目是否合法?例如,

//Assume that the names list as duplicate entries
List<String> names = ....
for (String name : names) {
// Do something
while (names.remove(name));
}
528898 次浏览

你不会想这么做的。它可能导致未定义的行为,具体取决于集合。您希望直接使用迭代器。虽然for每个构造都是语法糖,并且实际上使用了迭代器,但它对代码隐藏了迭代器,因此您无法访问它来调用Iterator.remove

迭代器的行为是 未指定,如果基础 属性时,将对集合进行修改 迭代正在以任何方式进行

相反,编写代码:

List<String> names = ....
Iterator<String> it = names.iterator();
while (it.hasNext()) {


String name = it.next();
// Do something
it.remove();
}

注意,代码调用Iterator.remove,而不是List.remove

附录:

即使您正在删除一个尚未遍历的元素,您仍然不希望修改集合,然后使用Iterator。它可能会以一种令人惊讶的方式修改集合,并影响未来对Iterator的操作。

为了在迭代集合时安全地从集合中删除,您应该使用迭代器。

例如:

List<String> names = ....
Iterator<String> i = names.iterator();
while (i.hasNext()) {
String s = i.next(); // must be called before you can call i.remove()
// Do something
i.remove();
}

Java文档:

该类的迭代器和listIterator返回的迭代器 方法是快速失败的:如果列表在任何地方被结构修改 方法创建迭代器后的任何时间 迭代器自己的remove或add方法时,迭代器将抛出 并发修改异常因此,面对并发 修改后,迭代器会快速而干净地失败,而不是 在不确定的时间冒着武断的、不确定的行为的风险

.在未来

也许许多新手不清楚的是,使用for/foreach构造隐式遍历列表会创建一个不可访问的迭代器。这个信息可以在在这里找到

“增强型for循环”的java设计是不向代码公开迭代器,但安全删除项的唯一方法是访问迭代器。所以在这种情况下,你得用老办法:

 for(Iterator<String> i = names.iterator(); i.hasNext();) {
String name = i.next();
//Do Something
i.remove();
}

如果在实际代码中,增强的for循环确实值得这样做,那么您可以将这些项添加到临时集合中,并在循环之后调用列表上的removeAll。

EDIT(重编):不,在迭代时以iterator.remove()方法之外的任何方式更改列表都会导致问题。解决这个问题的唯一方法是使用CopyOnWriteArrayList,但这实际上是为了解决并发问题。

删除重复项最便宜的方法(就代码行数而言)是将列表转储到LinkedHashSet中(如果需要,再转储回list中)。这样可以在删除重复项的同时保留插入顺序。

那些说你不能安全地从集合中删除一个项的说法是不正确的,你可以使用一个并发集合,比如ConcurrentHashMap。

是的,你可以使用for-each循环, 要做到这一点,你必须维护一个单独的列表来保存删除项目,然后使用removeAll()方法从名称列表中删除该列表,

List<String> names = ....


// introduce a separate list to hold removing items
List<String> toRemove= new ArrayList<String>();


for (String name : names) {
// Do something: perform conditional checks
toRemove.add(name);
}
names.removeAll(toRemove);


// now names list holds expected values
  1. 试试这个。并将条件更改为“WINTER”,你会想:
public static void main(String[] args) {
Season.add("Frühling");
Season.add("Sommer");
Season.add("Herbst");
Season.add("WINTER");
for (String s : Season) {
if(!s.equals("Sommer")) {
System.out.println(s);
continue;
}
Season.remove("Frühling");
}
}

我不知道迭代器,但是直到今天我才从循环中的列表中删除元素:

List<String> names = ....
for (i=names.size()-1;i>=0;i--) {
// Do something
names.remove(i);
}

这总是有效的,并且可以在其他不支持迭代器的语言或结构中使用。

确保这不是代码的味道。有可能颠倒逻辑,“包容”而不是“排他”吗?

List<String> names = ....
List<String> reducedNames = ....
for (String name : names) {
// Do something
if (conditionToIncludeMet)
reducedNames.add(name);
}
return reducedNames;

将我引导到这个页面的情况涉及到使用indecies从List中删除元素循环遍历List的旧代码。我想重构它以使用foreach样式。

它循环遍历整个元素列表,以验证用户有权限访问哪些元素,并从列表中删除没有权限的元素。

List<Service> services = ...
for (int i=0; i<services.size(); i++) {
if (!isServicePermitted(user, services.get(i)))
services.remove(i);
}

要反转此操作而不使用remove:

List<Service> services = ...
List<Service> permittedServices = ...
for (Service service:services) {
if (isServicePermitted(user, service))
permittedServices.add(service);
}
return permittedServices;

什么时候“remove”更合适?一个考虑因素是,如果给定一个大列表或昂贵的“添加”,与列表大小相比,只删除了一些内容。只做少量的删除可能比大量的添加更有效。但在我的案例中,情况并不值得这样的优化。

当您想要从列表中删除元素时,最好使用迭代器

因为删除的源代码是

if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null;

所以,如果你从列表中删除一个元素,列表将被重构,其他元素的索引将被改变,这可能会导致你想要发生的事情。

for (String name : new ArrayList<String>(names)) {
// Do something
names.remove(nameToRemove);
}

克隆列表names,并在从原始列表中删除时遍历克隆列表。比上面的答案简洁一点。

使用

.remove() Interator或

使用

CopyOnWriteArrayList