【发布时间】:2011-01-21 13:34:30
【问题描述】:
这是在 Java 中使用 for each 循环从 LinkedList 中查找和删除项目的有效方法吗?是否可能会出现不一致:
for(ObjectType ob : obList) {
if(ob.getId() == id) {
obList.remove(ob);
break;
}
}
【问题讨论】:
标签: java linked-list
这是在 Java 中使用 for each 循环从 LinkedList 中查找和删除项目的有效方法吗?是否可能会出现不一致:
for(ObjectType ob : obList) {
if(ob.getId() == id) {
obList.remove(ob);
break;
}
}
【问题讨论】:
标签: java linked-list
CopyOnWriteArrayList 可能是您正在寻找的。执行可变操作时,会制作底层数组的副本。这允许在 for-each 循环内修改列表元素。请记住,虽然这不是一个链表,而且效率很低。
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Main {
public static void main(String[] args) {
List<String> myList = new CopyOnWriteArrayList<String>();
myList.add("a");
myList.add("b");
myList.add("c");
// Will print [a, b, c]
System.out.println(myList);
for (String element : myList) {
if (element.equals("a")) {
myList.remove(element);
}
}
// Will print [b, c]
System.out.println(myList);
}
}
【讨论】:
上面的第二个循环应该稍微改一下
for (int i = 0; i < obList.size(); ) {
if (obList.get(i).getId() == id) {
obList.remove(i);
continue
}
++i;
}
或
for (int i = obList.size() - 1; i >= 0; --i) {
if (obList.get(i).getId() == id) {
obList.remove(i);
}
}
【讨论】:
最好使用迭代器并在搜索对象时使用它的 remove 方法,通过迭代集合来删除它。这是因为
原则上,我建议放弃增强的 for 并改用类似的东西:
for(Iterator<ObjectType> it=obList.iterator(); it.hasNext(); ) {
if(it.next().getId()==id) {
it.remove();
break;
}
}
这样您就不会对未来可能发生变化的基础列表做出假设。
比较代码以删除迭代器 remove 调用的最后一个条目(格式化 Sun 的):
private E remove(Entry<E> e) {
if (e == header)
throw new NoSuchElementException();
E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
e.next = e.previous = null;
e.element = null;
size--;
modCount++;
return result;
}
反对 remove(Object) 必须做什么:
public boolean remove(Object o) {
if (o==null) {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (e.element==null) {
remove(e);
return true;
}
}
} else {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
【讨论】:
其他人提到了正确的观点,通常这不是你如何remove 来自集合的对象。但是,在这种情况下,您 break 在您 remove 之后退出循环就可以了。
但是,如果您想在 remove 之后继续迭代,则需要使用迭代器。否则你会得到一个ConcurrentModificationException,或者在更一般的情况下,是未定义的行为。
所以是的,如果你在remove 之后将break 排除在foreach 之外,你会没事的。
对于那些说这将失败的人,因为您无法修改 foreach 中的集合——只有当您想继续迭代时,这才是正确的。这里不是这样,所以这个快捷方式很好。
ConcurrentModificationException 被迭代器检查并抛出。在这里,在remove(符合并发修改的条件)之后,您将break 退出循环。迭代器甚至没有机会检测到它。
最好在break上添加评论,为什么绝对必要等等,因为如果以后修改这段代码以在remove之后继续迭代,它会失败.
我会将这个习惯用法与goto 类似(或者更确切地说,标记为break/continue):一开始它可能看起来是错误的,但如果使用得当,它会使代码更简洁。
【讨论】:
编辑:确实,由于中断,它不会失败。有关详细信息,请参阅 polygenelubricant 的答案。
但是,这样做很危险。要在 Java 中同时迭代和修改集合,必须使用“ListIterator”对象,并使用迭代器自己的“add()”和“remove()”方法,而不是使用集合上的方法。
您可以查看“java.util.Iterator”和“java.util.ListIterator”类的 java 文档
【讨论】:
break。此代码不会失败。
要避免 ConcurrentModifationException,您可以这样做:
final Iterator<ObjectType> i = obList.iterator();
while (i.hasNext()) {
if (i.next().getId() == id) {
i.remove();
}
}
或
for (int i = 0; i < obList.size(); i++) {
if (obList[i].getId() == id) {
obList.remove(i);
}
}
我更喜欢第一个。处理索引更容易出错,并且可以有效地实现迭代器。第一个建议适用于 Iterable,而第二个建议需要 List。
【讨论】:
你应该使用iterator.remove():
从基础集合中移除 由返回的最后一个元素 迭代器(可选操作)。这 每个方法只能调用一次 打电话给下一个。一个人的行为 迭代器是未指定,如果 基础集合被修改 当迭代正在进行时 除了调用这个以外的任何方式 方法。
【讨论】:
试试这样的:
Iterator<ObjectType> iter = obList.iterator();
while (iter.hasNext()) {
ObjectType ob = iter.next();
if(ob.getId() == id) {
iter.remove();
break;
}
}
这是迭代器不能被 foreach 循环替换的最后一个地方。
【讨论】:
break 后面加上remove,则不必如此冗长。