【问题标题】:LinkedList: remove an objectLinkedList:删除一个对象
【发布时间】: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


    【解决方案1】:

    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);
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      上面的第二个循环应该稍微改一下

      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);
          }
      }
      

      【讨论】:

        【解决方案3】:

        最好使用迭代器并在搜索对象时使用它的 remove 方法,通过迭代集合来删除它。这是因为

        1. 例如,该集合可以是一个链表(在您的情况下是),其删除方法意味着重新搜索对象,该搜索可能具有 O(n) 复杂度。
        2. 除非您使用迭代器的 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;
        }
        

        【讨论】:

        • 你没有回答我的问题,但这是很好的洞察力。
        【解决方案4】:

        其他人提到了正确的观点,通常这不是你如何remove 来自集合的对象。但是,在这种情况下,您 break 在您 remove 之后退出循环就可以了。

        但是,如果您想在 remove 之后继续迭代,则需要使用迭代器。否则你会得到一个ConcurrentModificationException,或者在更一般的情况下,是未定义的行为。

        所以是的,如果你在remove 之后将break 排除在foreach 之外,你会没事的


        对于那些说这将失败的人,因为您无法修改 foreach 中的集合——只有当您想继续迭代时,这才是正确的。这里不是这样,所以这个快捷方式很好。

        ConcurrentModificationException 被迭代器检查并抛出。在这里,在remove(符合并发修改的条件)之后,您将break 退出循环。迭代器甚至没有机会检测到它。

        最好在break上添加评论,为什么绝对必要等等,因为如果以后修改这段代码以在remove之后继续迭代,它会失败.

        我会将这个习惯用法与goto 类似(或者更确切地说,标记为break/continue):一开始它可能看起来是错误的,但如果使用得当,它会使代码更简洁。

        【讨论】:

        • 我没有检查语言规范,但 foreach 语法不只是在幕后使用迭代器的编译器魔法吗?
        • 是的,它确实在幕后使用了一个迭代器,而且这个迭代器对你隐藏了。
        • 但是在未来的版本中很有可能会失败,因为下一个开发人员试图添加一些功能而没有“看到”那个陷阱。那么你应该清楚地记录它。
        • 如果这是一个链表,那么 Iterator.remove() 比 List.remove(Object) 高效得多,因为后者必须重新搜索对象。我会使用迭代器,原则上它会被删除。
        【解决方案5】:

        编辑:确实,由于中断,它不会失败。有关详细信息,请参阅 polygenelubricant 的答案。

        但是,这样做很危险。要在 Java 中同时迭代和修改集合,必须使用“ListIterator”对象,并使用迭代器自己的“add()”和“remove()”方法,而不是使用集合上的方法。

        您可以查看“java.util.Iterator”和“java.util.ListIterator”类的 java 文档

        【讨论】:

        • “Iterator”没有“add()”方法。只有 ListIterator 有。你应该指出这一点。
        • @Zorglub,关注break。此代码不会失败。
        • 它可能不会“失败”,但代码在任何正常意义上都是错误的。
        • @Tom 这就是我自己的感受!
        【解决方案6】:

        要避免 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。

        【讨论】:

        • 只需在 for 循环中使用迭代器即可。
        • 这将使循环的最后一部分为空。我不喜欢那样。一段时间非常适合那里。
        • 此代码可能会进行多次删除。这是与原始代码不同的行为,它最多删除一个元素。
        • @polygenelubricants 你是对的,但 getId() 意味着每个 id 只有一个元素。
        【解决方案7】:

        你应该使用iterator.remove():

        从基础集合中移除 由返回的最后一个元素 迭代器(可选操作)。这 每个方法只能调用一次 打电话给下一个。一个人的行为 迭代器是未指定,如果 基础集合被修改 当迭代正在进行时 除了调用这个以外的任何方式 方法。

        【讨论】:

        • “在迭代过程中”——这是这里的重要区别。在他的例子中,一旦他修改了集合,他就会中止迭代。这就是我们进行这个讨论的原因。不过,对于一般情况,您是绝对正确的。
        【解决方案8】:

        试试这样的:

        Iterator<ObjectType> iter = obList.iterator();
        while (iter.hasNext()) {
          ObjectType ob = iter.next();
          if(ob.getId() == id) {
            iter.remove();
            break;
          }
        }
        

        这是迭代器不能被 foreach 循环替换的最后一个地方。

        【讨论】:

        • 如果您在break 后面加上remove,则不必如此冗长。
        • @polygenelubricants :哦,你是对的。我没有意识到异常只会在下一个循环中发生。
        • 如果这是一个链表,那么 Iterator.remove() 比 List.remove(Object) 高效得多,因为后者必须重新搜索对象。我会使用迭代器,原则上它会被删除。
        • @Software Monkey,所以这意味着我在问题中提出的代码可以写成 obList.remove(ob)
        • @Xolve:假设(a)您的集合很小,或者您可以容忍对刚刚找到的对象的额外搜索,并且(b)您在删除后中断并且不继续迭代。请参阅我的答案以了解更多详细信息。
        猜你喜欢
        • 2014-08-14
        • 1970-01-01
        • 2019-08-01
        • 2012-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多