List.remove奇怪的行为

时间:2014-11-24 17:48:51

标签: java

注意:与此问题不重复:Why am I not getting a java.util.ConcurrentModificationException in this example?。问题是,为什么异常不是被抛出。

如果我们在foreach上使用List<String>并尝试从中删除任何元素,那么它会抛出java.util.ConcurrentModificationException,但为什么以下代码不会抛出相同的异常而且也不会处理第二个对象User

public class Common {

    public static void main(String[] args) {
        User user1 = new User();
        user1.setFirstname("Vicky");
        user1.setLastname("Thakor");

        User user2 = new User();
        user2.setFirstname("Chirag");
        user2.setLastname("Thakor");

        List<User> listUser = new ArrayList<User>();
        listUser.add(user1);
        listUser.add(user2);

        int count = 0;
        for (User user : listUser) {
            System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
            count++;
            listUser.remove(user);
        }
    }
}

输出结果为:

  

0:Vicky Thakor

4 个答案:

答案 0 :(得分:4)

虽然问题并非完全重复,但链接的问题:Why am I not getting a java.util.ConcurrentModificationException in this example?包含答案。

当迭代器的next()方法调用时,会进行验证是否抛出异常的检查,但仅当hasNext()返回true时才会发生这种情况。在您的情况下,当列表包含两个元素并在第一次迭代中删除第一个元素时,条件为:

public boolean hasNext() {
        return cursor != size(); // 1 != 1 after first iteration
}

意外false,因为cursor位于1,而size()目前是next()。因此不会调用listUser.add(user2); ,并且不会抛出异常。

添加第三个元素:

{{1}}

并抛出异常。但是,您不应该依赖于这种行为,因为正如the documentation所解释的那样,不能保证:

  

请注意,无法保证快速失败的行为,因为一般来说,在存在不同步的并发修改时,无法做出任何硬性保证。失败快速操作会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的正确性是错误的:ConcurrentModificationException应该仅用于检测错误。

答案 1 :(得分:2)

正如NESPowerGlove在注释部分中所述,interator根据java doc返回一个快速失败的迭代器,但它包含了这个

  

请注意,无法保证迭代器的快速失败行为   一般来说,不可能做出任何硬性保证   存在不同步的并发修改。快速失败   迭代器抛出ConcurrentModificationException就是尽力而为   基础。因此,编写一个依赖的程序是错误的   关于它的正确性的这个例外:快速失败的行为   迭代器应该只用于检测错误。

(强调我的) 因此,无法保证在修改的情况下会抛出异常。

答案 2 :(得分:0)

您的循环可以重写为

    Iterator<User> iterator = listUser.iterator();
    while (iterator.hasNext()) {
        User user = iterator.next();
        System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
        listUser.remove(user);
    }

如果我们展开循环,它将看起来像

    Iterator<User> iterator = listUser.iterator();
    System.out.println(iterator.hasNext()); // prints true, loop executes
    User user = iterator.next();
    System.out.println(count + ":" + user.getFirstname() + " " + user.getLastname());
    listUser.remove(user);

    System.out.println(iterator.hasNext()); // prints false, loop stops

直到第二次调用iterator.hasNext(),才会修改集合,因此所有工作都按预期工作。现在问题是为什么第二个iterator.hasNext()调用返回false而不是抛出ConcurrentModificationException?让我们检查一下ArrayList来源。我将引用JDK 8来源。

ArrayList.java,ArrayList&#39;迭代器,在Itr类的第840行声明。它的hasNext()方法非常简单:

    int cursor;       // index of next element to return
    ...

    public boolean hasNext() {
        return cursor != size;
    }

cursor是要返回的下一个元素的索引,size属于外部ArrayList实例。

检查next()方法中实现的编码。

请注意,您不得在自己的代码中依赖此检查。它旨在打击错误,而不是为您提供依赖的逻辑。并且它无法保证能够捕获所有错误(如您的问题所示)。可能性能是为什么在hasNext()方法中没有实现此检查的原因。

答案 3 :(得分:0)

试试这个:

int count = 0;
for (User user : listUser) {
   listUser.remove(user);
   System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
   count++;
}
for (User user : listUser) {
   listUser.remove(user);
   System.out.println(count + ":" + user.getFirstname()+" "+ user.getLastname());
   count++;
}

我在不到10秒的时间内创建了User类。包含两个字符串。但是对于这个问题,User类是无关紧要的。假设Object并且问题仍然存在。关键是在调试时运行它,看看会发生什么。

如果您为每个循环添加相同的内容并尝试迭代同一个列表,您将看到ConcurrentModificationException。第二次,它会显示:

0:Vicky Thakor
1:Chirag Thakor

然后抛出异常。很多人都没记住ConcurrentModificationExceptionRuntimeException,因此,remove()方法上的API没有记录。这里的问题是你应该信任文档。迭代时删除元素的文档安全方法是使用Iterator