【问题标题】:List iterator illegal state exception [duplicate]列出迭代器非法状态异常[重复]
【发布时间】:2016-12-14 20:49:33
【问题描述】:

有这个课

public class IteratorStuff {

    private static final String EMPTY = "";

    public static void main(String[] args) {
        System.out.println("success:");
        success(newCollection());
        System.out.println("fail:");
        fail(newCollection());
    }

    private static void fail(Collection<String> myCollection) {
        Iterator<String> iterator = myCollection.iterator();
        iterator.forEachRemaining(new Consumer<String>() {
            public void accept(String s) {
                if (s != EMPTY)
                    System.out.println("string = " + s);
                else
                    iterator.remove();
            }
        });
    }

    private static Collection<String> newCollection() {
        Collection<String> myList = new LinkedList<String>();
        myList.add(EMPTY);
        myList.add("1");
        myList.add("2");
        myList.add("3");
        return myList;
    }

    private static void success(Collection<String> myCollection) {
        Iterator<String> iterator = myCollection.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            if (s != EMPTY)
                System.out.println("string = " + s);
            else
                iterator.remove();
        }
    }
}

它遍历字符串集合并删除特定的 EMPTY 字符串并打印其他字符串。 success(Collection) 实现工作正常。

失败者因 IllegalStateException 而中断。但是,它能够从迭代器中获取 EMPTY 字符串。这表明必须调用 next() 。另外,在默认的 forEachRemaining 实现中

default void forEachRemaining(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    while (hasNext())
        action.accept(next());
}

next() 被调用并将任何元素传递给 action.accept()。另一方面,我似乎也找不到 LinkedList 返回的迭代器的实现。

这是一个错误吗?如何返回第一个元素并仍然导致 IllegalStateException?

此外,只有当第一个元素是 EMPTY 字符串时才会发生这种情况。

【问题讨论】:

  • 因为 forEachRemaining 是迭代器中的默认方法。真的不明白你在问什么......
  • 我不希望 iterator.remove()forEachRemaining 的上下文中工作,因为它可能没有在内部使用迭代器。它在success 的情况下工作正常,因为您正在 使用迭代器并删除当前元素。但是,我没有您的文档参考,因此我将其发布为评论。由于forEachRemaining() 没有指定它的迭代方式,所以没有理由让代码中的Iterator 可用。
  • @JimGarrison 是的,我意识到它成功运行良好,因为它现在已按预期使用了很长时间。然而,forEachRemaining(最后在默认实现中)确实调用了 next() 并将结果传递给操作。
  • 有趣的问题。我喜欢没有立即明显答案的问题,让我去看看GrepCode :-)

标签: java list iterator


【解决方案1】:

对于未来的读者:此答案不正确!

即使提问者已接受此答案作为其问题的解决方案,但它可能 [对其他人或一般而言] 不起作用。请参阅 Andreas 的 this answer 以更全面地分析问题。

如果您查看 GrepCode 中 LinkedList$ListItr 的代码(LinkedList#iterator() 返回的 ListIterator 实现),您会发现它不会更新迭代器本身,而是从当前元素开始并进行迭代使用局部变量。

这意味着您从未调用过next() 的迭代器本身是无效的。即使您在进入循环之前确实调用了next(),它也会删除错误的元素,并且还可能导致ConcurrentModificationException,因为它的位置没有被forEachRemaining() 更新,并且项目删除会干扰迭代器。

&lt;soapbox&gt;
对于 Javadoc 无法解决的有关 Java 库的任何问题,GrepCode 是首选资源。使用它。
&lt;/soapbox&gt;

【讨论】:

  • 你在看什么源代码? openjdk 源不使用LinkedList$ListItr.forEachRemaining() 中的局部变量。见my answer
  • 非常有趣....你是对的。 OP 应该接受您的回答,以进行更彻底的分析。
【解决方案2】:

问题在于您使用的是LinkedList,它有自己的有缺陷forEachRemaining() 实现。

Source:

public void forEachRemaining(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    while (modCount == expectedModCount && nextIndex < size) {
        action.accept(next.item);
        lastReturned = next;
        next = next.next;
        nextIndex++;
    }
    checkForComodification();
}

使用 default 实现,直到 next() 返回后才会调用 accept() 方法。

public E next() {
    checkForComodification();
    if (!hasNext())
        throw new NoSuchElementException();

    lastReturned = next;
    next = next.next;
    nextIndex++;
    return lastReturned.item;
}

由于remove() 会检查lastReturned 的值,因此需要在调用accept() 之前设置该值。

public void remove() {
    checkForComodification();
    if (lastReturned == null)
        throw new IllegalStateException();

    Node<E> lastNext = lastReturned.next;
    unlink(lastReturned);
    if (next == lastReturned)
        next = lastNext;
    else
        nextIndex--;
    lastReturned = null;
    expectedModCount++;
}

如前所述,forEachRemaining() 实现存在错误。应该是:

public void forEachRemaining(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    while (modCount == expectedModCount && nextIndex < size) {
        lastReturned = next;
        next = next.next;
        nextIndex++;
        action.accept(lastReturned.item);
    }
    checkForComodification();
}

提交错误!


更新

ArrayList$Itr.forEachRemaining() 也有类似的问题,因为 cursorlastRet 在迭代过程中没有设置,所以虽然 forEachRemaining() 的 javadoc 没有明确说不能使用 Iterator.remove()ListIterator.add(),但当前实现显然没想到你会这样做。

它们甚至不会以一致的方式失败或保护,因此它们与正常的快速失败策略不一致。

因此,也许为文档和/或快速失败逻辑提交错误会更合适。

【讨论】:

  • 它是否违反了迭代器契约?迭代器接口未指定在forEachRemaining 方法期间访问迭代器是有效的。如果在 forEachRemaining 返回时迭代器的状态是一致的,我会说它不属于错误。
  • @Sam 它也没有说你不能,并且鉴于代码的简单重新排序将解决问题,我会说这是一个无意的错误。如果没有,为什么还要更新lastReturned?现在,如果您尝试删除 first 元素,它会失败,但适用于任何其他元素。
  • lastReturned 必须以任何一种方式更新,以表明迭代器已被使用。
  • 不过,我想我同意。尽可能让事情变得更简单总是更好。不过,我想知道它是否对任何其他迭代器方法有影响。由于这是一个列表迭代器,如果在forEachRemaining 期间调用add 会怎样?还是previous
  • @Sam lastReturned 不表示迭代器是否已被使用。它只是持有对next() 返回的最后一个元素的迭代器位置引用,因此remove() 可以删除它。在第一次调用next() 之前和调用remove() 之后是null。它与迭代器的结束逻辑无关。
【解决方案3】:

问题可能是—— 您正在处理迭代器并同时对其进行修改。

private static void fail(Collection<String> myCollection) {
    Iterator<String> iterator = myCollection.iterator();
    iterator.forEachRemaining(new Consumer<String>() {
        public void accept(String s) {
            if (s != EMPTY)
                System.out.println("string = " + s);
            else
                iterator.remove();
        }
    });
}

您正在使用迭代器对象调用 forEachRemaining 方法,并且在其中您还从同一个迭代器中删除了该对象。

【讨论】:

  • 无法在答案部分发表评论。
  • 这是一个很好的观点。 Consumer 方法的主体应被视为单个操作。在该操作期间,迭代器的状态是未定义的。如果您在消费者方法运行时尝试在其上调用其他方法,则迭代器完全有权感到不安。
  • 在这种情况下,您的正常 while hasNext 循环的迭代应该被视为单个操作?我不确定这是否有意义。
猜你喜欢
  • 1970-01-01
  • 2019-09-29
  • 1970-01-01
  • 2023-03-23
  • 2018-06-15
  • 1970-01-01
  • 1970-01-01
  • 2020-04-29
  • 2013-01-13
相关资源
最近更新 更多