当调用Collection.remove时,ArrayList.remove会给出不同的结果

时间:2016-02-28 09:08:16

标签: java collections iteration

此代码:

$("#sidebar > ul > li").each(function() {
    var sideItem = $(this);
    if (sideItem.find("a").attr("href") == location.pathname) {
      sideItem.addClass("activation");
    }
});

$('#sidebar li.active').addClass('open').children('ul').show();
$('#sidebar li.sub>a').on('click', function(){
    $(this).removeAttr('href');
    var element = $(this).parent('li');
    if (element.hasClass('open')) {
        element.removeClass('open');
        element.find('li').removeClass('open');
        element.find('ul').slideUp(200);
    }
    else {
        element.addClass('open');
        element.children('ul').slideDown(200);
        element.siblings('li').children('ul').slideUp(200);
        element.siblings('li').removeClass('open');
        element.siblings('li').find('li').removeClass('open');
        element.siblings('li').find('ul').slideUp(200);
    }
});

打印: Collection<String> col = new ArrayList<String>(); col.add("a"); col.add("b"); col.add("c"); for(String s: col){ if(s.equals("b")) col.remove(1); System.out.print(s); }

与此同时:

abc

打印: ArrayList<String> col = new ArrayList<String>(); col.add("a"); col.add("b"); col.add("c"); for(String s: col){ if(s.equals("b")) col.remove(1); System.out.print(s); }

然而它应该打印相同的结果...... 问题是什么?

3 个答案:

答案 0 :(得分:30)

Collection只有boolean remove(Object o)方法,如果找到则删除传递的对象。

ArrayList也有public E remove(int index),可以通过索引删除元素。

您的第一个代码段调用boolean remove(Object o),但不会删除任何内容,因为您的ArrayList不包含1。您的第二个代码段调用public E remove(int index)并删除索引为1的元素(即删除"b")。

不同的行为是由于方法重载解析在编译时发生并且取决于您为其调用方法的变量的编译时类型而导致的。当col的类型为Collection时,remove接口的Collection方法(以及该接口继承的方法)将被视为重载解析。

如果您将col.remove(1)替换为col.remove("b"),则两个代码段的行为都相同。

正如Tamoghna Chowdhury评论的那样,boolean remove(Object o)可以接受原始论证 - int在你的情况下 - 由于int自动装箱到Integer实例。对于第二个代码段,选择public E remove(int index)而不是boolean remove(Object o)的原因是重载解析过程的方法首先尝试查找匹配方法而不进行自动装箱/取消装箱转换,因此它只考虑{{1 }}

答案 1 :(得分:9)

要在迭代时Collection安全删除,您应该使用Iterator

ArrayList<String> col = new ArrayList<String>();    
col.add("a");
col.add("b");
col.add("c");

Iterator<String> i = col.iterator();
while (i.hasNext()) {
   String s = i.next(); // must be called before you can call remove
   if(s.equals("b"))
      i.remove();
   System.out.print(s);
}

关于,ArrayList工作时,从集合中删除不适用的原因是由于以下原因:

  1. java.util.ArrayList.remove(int index)方法删除此列表中指定位置的元素。将任何后续元素向左移位(从索引中减去一个)。因此,这个适合你。

  2. java.util.Collection.remove(Object o)方法从此集合中删除指定元素的单个实例(如果存在)(它是可选操作)。更正式地,如果此集合包含一个或多个此类元素,则删除元素e,使(o==null ? e==null : o.equals(e))。如果此集合包含指定的元素,则返回true(或等效地,如果此集合因调用而更改)。

  3. 希望,这有帮助。

答案 2 :(得分:2)

两个片段都以不同的方式被破坏了!

案例1(Collection<String> col):

由于Collection未编入索引,因此其接口公开的唯一remove方法是Collection.remove(Object o),它会删除指定的相等对象。执行col.remove(1);首先调用Integer.valueOf(1)以获取Integer对象,然后要求列表删除该对象。由于列表不包含任何Integer个对象,因此不会删除任何内容。迭代通常在列表中继续,并打印出abc

案例2(ArrayList<String> col):

col的编译时类型为ArrayList时,调用col.remove(1);会调用方法ArrayList.remove(int index)来删除指定位置的元素,从而删除{{ 1}}。

现在,为什么不打印b?为了使用c语法循环集合,它在后台调用集合来获取for (X : Y)对象。对于Iterator(以及大多数集合)返回的Iterator,在迭代期间对列表执行结构修改是不安全的 - 除非您通过以下方法修改它ArrayList本身 - 因为Iterator会变得混乱并且无法跟踪下一个要返回的元素。这可能导致元素被多次迭代,元素被跳过或其他错误。这就是这里发生的事情:元素Iterator出现在列表中但从未打印过,因为您混淆了c

Iterator能够检测到此问题时,它会通过抛出Iterator来警告您。但是,ConcurrentModificationException对问题的检查是针对速度进行优化的,而不是100%正确性,并且它并不总能检测到问题。在您的代码中,如果您将Iterator更改为s.equals("b")s.equals("a"),则会抛出异常(尽管这可能取决于特定的Java版本)。来自ArrayList documentation

  

此类的s.equals("c")iterator方法返回的迭代器是 fail-fast :如果在创建迭代器后的任何时候对列表进行结构修改,则任何除了通过迭代器自己的listIteratorremove方法之外,迭代器将抛出add。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒着任意的,非确定性行为的风险。

     

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬性保证。失败快速的迭代器会尽最大努力抛出ConcurrentModificationException

要在迭代期间删除元素,您必须使用ConcurrentModificationException方法将for (X : Y) - 样式的循环更改为显式Iterator上的手动循环:

remove

现在这完全安全了。它将完全迭代所有元素一次(打印for (Iterator<String> it = col.iterator(); it.hasNext();) { String s = it.next(); if (s.equals("b")) it.remove(); System.out.print(s); } ),而元素abc将被删除。

如果您愿意,如果在任何删除后仔细调整索引,则可以在b使用Iterator - 样式循环的情况下实现相同的效果:

int i