删除元素时ArrayList hasNext的行为

时间:2017-05-05 11:36:51

标签: java arraylist

我要从ArrayList中删除元素,请考虑以下代码:

private static List<Person> persons = new ArrayList<>();
     //initialize the persons arraylist.

     for (Person p : persons) {
        if (p.getAge() < 18) {
            persons.remove(p);
        }
    }

初始化方法:

private static void initialize() {

    persons.add(new Person("Rahul", 25, Gender.MALE, Citizenship.INDIA));
    persons.add(new Person("Sally", 21, Gender.FEMALE, Citizenship.USA));
    persons.add(new Person("Ben", 35, Gender.MALE, Citizenship.CANADA));
    persons.add(new Person("Wayne", 30, Gender.MALE, Citizenship.UK));
    persons.add(new Person("ShaneYoung", 18, Gender.MALE, Citizenship.AUSTRALIA));
    persons.add(new Person("Kimmo", 17, Gender.MALE, Citizenship.FINLAND));
    persons.add(new Person("Simo", 17, Gender.MALE, Citizenship.FINLAND));

}

输出: [Rahul,Sally,Ben,Wayne,ShaneYoung,Simo]

请注意,理想情况下不应返回倒数第二个和最后一个项目。 我理解如果我们尝试在迭代它时修改ArrayList的结构,我们可能会得到一个ConcurrentModificationException。我指的是ArrayList中的remove(Object o)。这当然不是迭代时删除元素的推荐方法。在大多数情况下抛出异常。但是,如果我有一个列表,其中第二个项目满足我的删除条件,并且最后一个项目也满足删除条件,则不会抛出ConcurrentModificationException,因为hasNext方法返回false。 hasNext()的代码是:

return cursor != size 

在这种情况下,cursor == size。但请注意,最后一个元素也会被跳过或不被处理。因为跳过最后一个元素但也没有异常,但也会在输出中返回。

我已经查看了this错误,并查看了链接到的重复错误。他们似乎突出了一个稍微不同的场景。

当我们调用Iterator.remove时,它似乎将光标位置调整为lastRet。所以使用Iterator.remove即使有满足删除条件的第二个最后一个项也可以正常工作但是remove(Object o)反过来调用fastRemove似乎没有设置它,因此完全跳过最后一个元素。

我知道Iterator.remove javadoc提到了未指定的行为,上面提到的错误中提到的注释也表明ConcurrentModificationException被抛出作为尽力而为。但考虑到上面的代码,最后一个元素未被过滤掉的部分,这不是一个应该以更好的方式处理的错误或场景吗?

我已经注意到了另一个question,但答案只解释了如何根据我上面提到的当前实现来实现输出。我的问题更多的是关于是否应该更改hasNext实现来处理这种情况?

4 个答案:

答案 0 :(得分:2)

根据JLS #14.14.2,增强的for循环等同于使用迭代器。

正如你所提到的,Iterator::remove的javadoc非常清楚:

  

如果在迭代进行过程中以除了调用此方法之外的任何方式修改基础集合,则未指定迭代器的行为。

所以不,它不是一个错误。

答案 1 :(得分:0)

当您尝试在循环中删除列表的元素时,它会被窃听。 我通常做的是声明另一个临时列表,以便你可以使用removeAll方法

List<Person> temp = new List<Person>();
     for (Person p : persons) {
        if (p.getAge() < 18) {
            temp.add(p);
        }
      }

persons.removeAll(temp);

答案 2 :(得分:0)

您可以使用ListIterator删除项目,因为它有remove方法:

ListIterator<Person> iter = persons.listIterator();
while (iter.hasNext()) {
    Person p = iter.next();
    if (p.getAge() < 18) {
        iter.remove();
    }
}

但我会推荐使用assylias建议的Java 8解决方案:

persons.removeIf(p -> p.getAge() < 18);

答案 3 :(得分:0)

我认为你已经知道一种或多种获得你想要的好方法。所以我会非常直截了当地提出你的问题。

  

但考虑到上面的代码,最后一个元素不是的部分   过滤掉,这不是一个应该处理的错误或场景   以更好的方式?

定义'bug'。当我多年前教过测试时,我的教科书定义了一个错误,因为任何行为都不符合用户可以合理预期的行为。我应该说这是这个定义的错误。然而,在我看来,它似乎是the bugs you are linking to的一个版本,而不是一个新的但未被发现的版本。这些错误报告的存在也证实了这是一个错误。

我确实在assylias’ answer中看到了引用。它取决于面值和上下文,它允许你的迭代器,因此你的for循环,以任何方式表现,包括默认跳过列表的最后一个元素。但是这个引用是在Iterator.remove()的文档中,这是一个你没有使用的方法,所以我认为你期望你的循环处理列表中的所有元素或抛出ConcurrentModificationException(或者其他一些例外原因)。我本来期望的一样。

  

我的问题更多的是关于hasNext实现是否应该   改为处理这种情况?

似乎很容易在checkForComodification()中添加对hasNext()的调用,就像在next()remove()中一样。这会给你预期的行为。不过,值得三思,如果您所指的错误是固定的,我们可能不希望添加此调用。我没有想过所有后果。

即使我同意这是一个错误,改变在世界各地运行的数百万个程序中使用的类的行为很容易导致按预期工作的程序显示新Java版本的不需要的行为。甲骨文非常不愿意做出这样的改变。

他们可以做的最少恕我直言,将在ArrayList的文档中更清楚地记录错误。