你好,
Lists.partition 的实现似乎存在错误。问题如下;假设我对一个整数列表进行了分区,如果您对分区列表执行循环,并且对每个子列表执行 removeAll() 函数,则会收到错误 (NoSuchElementException)
代码如下:
似乎每当子列表被清空时,空的子列表就会从分区列表中删除,从而导致上面提到的异常,该异常是从 AbstractList.java 中的 next() 方法抛出的
你好,
Lists.partition 的实现似乎存在错误。问题如下;假设我对一个整数列表进行了分区,如果您对分区列表执行循环,并且对每个子列表执行 removeAll() 函数,则会收到错误 (NoSuchElementException)
代码如下:
似乎每当子列表被清空时,空的子列表就会从分区列表中删除,从而导致上面提到的异常,该异常是从 AbstractList.java 中的 next() 方法抛出的
这是一个有趣的失败模式?最终我怀疑文档中关于突变的警告适用:
内部列表...遵守有关修改的所有常见警告,如 [
List.sublist()
] 中所述。
话虽如此,这绝对是奇怪的行为。考虑一下:
jshell> var partitioned = Lists.partition(Lists.newArrayList(1), 2)
partitioned ==> [[1]]
jshell> var iter = partitioned.iterator()
iter ==> java.util.AbstractList$Itr@28c4711c
jshell> iter.hasNext()
$5 ==> true
jshell> iter.next().removeAll(Lists.newArrayList(1))
$6 ==> true
jshell> iter.hasNext()
$7 ==> true
jshell> iter.next()
| Exception java.util.NoSuchElementException
| at AbstractList$Itr.next (AbstractList.java:377)
| at (#8:1)
partitioned
仅以一个元素开始!为什么第二次调用时会iter.hasNext()
返回?true
我们可以用一个更简单、不涉及的例子来触发相同的行为Lists.partition()
:
jshell> var ls = Lists.newArrayList(1)
ls ==> [1]
jshell> var iter = ls.iterator()
iter ==> java.util.ArrayList$Itr@534df152
jshell> iter.hasNext()
$11 ==> true
jshell> iter.next()
$12 ==> 1
jshell> ls.remove(0)
$13 ==> 1
jshell> iter.hasNext()
$14 ==> true
不对!调用.next()
同样会失败,但会提供更多信息ConcurrentModificationException
。无论如何,这直接违反了 [Iterator
规范]( https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/Iterator.html#hasNext ()),其中规定:
如果[ ]返回元素而不是抛出异常,则
hasNext()
返回 true 。next()
好像AbstractList.Itr.hasNext()
有一个错误:
public boolean hasNext() {
return cursor != size();
}
true
因为如果cursor
大于,这将返回size()
。
我想知道是否有一个好的理由来!=
代替<
?
JavadocArrayList
指出,如果在迭代过程中修改了列表,迭代器将失败(抛出ConcurrentModificationException
),除非通过迭代器自己的remove()
或add()
方法(即通过调用)进行remove(0)
修改。它还指出,具体失败时间尚未指定。
基本上,如果您在迭代时修改非线程安全的列表,您应该预期 CME,但不可靠。
Lists.partition()
返回一个列表列表;每个内部列表都是原始列表子列表的动态视图,并且“遵守该 API 中解释的有关修改的所有常见警告”。
文档中hasNext()
说,如果返回true
则next()
不会抛出,实际上是指next()
不会抛出NoSuchElementException
,而文档中记录的是如果hasNext()
返回则会抛出false
。(我承认,文档中说“而不是抛出异常”应该澄清为“而不是抛出” NoSuchElementException
。)
我不确定我是否理解了你的最后一段。在我上面的例子中,hasNext()
返回true
然后next()
抛出NoSuchElementException
。即使对 上的文档Iterator.hasNext()
进行了调整以专门引用,它仍然似乎在断言如果返回NoSuchElementException
,则不应抛出此异常。hasNext()
true
好吧,我没有仔细阅读你的例子。这不是很好,但ArrayList
确实有效地表明,如果在迭代过程中修改了列表,则所有赌注都会失效。
如果我们想改变迭代器,我会说我们应该hasNext()
调用checkForComodification()
。
但是,除了 JDK 之外,我发现它Partition
没有使用AbstractList
使其抛出 CME 的功能。Partition
它本身不允许修改,但如果源列表被修改,它的迭代器可能会做出那样的反应。
@netdpb @dimo414 你好,看了你们的讨论后,我有以下一些想法。
根据讨论,你对iter.hasNext()为何为真感到困惑。实际上,这是因为Partition类使用了超类AbstractList中的Iterator。而AbstractList类中的hasNext()函数并不完善。它们已在子类ArrayList中进行了优化。请在下面查看
这是AbstractList类中的hasNext。它调用size()函数
public boolean hasNext() {
return cursor != size();
}
这是ArrayList类中的hasNext。它使用值 size。文档说它是AbstractList.Itr 的优化版本。我在 JDK1.8 中看到了这个
public boolean hasNext() {
return cursor != size;
}
当使用函数size()时,每次程序调用hasNext()。hasNext()会调用size(),然后size 的值会被重新计算。回到 #3570 的代码,当程序执行removeall()时,重新计算的 size 会是 0,但是游标是 1。所以hasNext()返回 true。
for(List<Integer> l : ll){
l.removeAll(elementExclude);
}
@netdpb @dimo414 有一个拼写错误,是 3790,而不是 3570。
我还测试了下面的代码。我使用 ArrayList 作为容器,然后执行相同的 removeAll。没有抛出任何异常。所以,我认为分区的行为应该相同
List<Integer> list = new ArrayList();
list.add(1);
List<Integer> elementExclude = new ArrayList();
elementExclude.add(1);
List<List<Integer>> ll = new ArrayList();
ll.add(list);
for(List<Integer> l : ll){
l.removeAll(elementExclude);
}
根据讨论,您对为什么 iter.hasNext() 为真感到困惑。
我认为你误解了;考虑到当前的实现,返回 true 的原因很清楚hasNext()
,但不清楚这种行为是否真正符合规范(我不认为是)。
@dimo414 好的,我想知道预期的行为是什么?在我看来,我认为预期的行为应该与 ArrayList 相同。不应抛出任何异常。
为了更清楚,我认为下面两个代码应该具有相同的行为
以分区作为容器的示例
List<Integer> list = new ArrayList();
list.add(1);
List<Integer> elementExclude = new ArrayList();
elementExclude.add(1);
List<List<Integer>> ll =Lists.partition(list, 5);
ll.add(list);
for(List<Integer> l : ll){
l.removeAll(elementExclude);
}
以ArrayList为容器的示例
List<Integer> list = new ArrayList();
list.add(1);
List<Integer> elementExclude = new ArrayList();
elementExclude.add(1);
List<List<Integer>> ll = new ArrayList();
ll.add(list);
for(List<Integer> l : ll){
l.removeAll(elementExclude);
}