Java 8 流 API:修改列表时出现异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/30590378/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8 stream API: Exceptions when modifying Lists
提问by rapucha
Let's take an ArrayList
and fill it with with something simple:
让我们ArrayList
用一个简单的东西填充它:
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(""+i);
}
I will try to remove one member, say named 5, with a different ways of stream API. For this I define the method, that will give me a ConcurentModificationException
when using traditional iteration with iterator.
我将尝试使用不同的流 API 方式删除一个成员,例如名为 5 的成员。为此,我定义了方法,ConcurentModificationException
当使用带有迭代器的传统迭代时,它会给我一个。
void removeMember(String clientListener) {
list.remove(clientListener);
}
This code gives me that exception, which I understand:
这段代码给了我那个例外,我理解:
list.parallelStream()
.filter(string -> string.equalsIgnoreCase("5"))
.forEach(string -> removeMember(string));
However, trying just stream()
, not parallelStream()
gives an null pointer exception (NPE), which is strange for me:
但是,尝试 just stream()
, notparallelStream()
给出空指针异常(NPE),这对我来说很奇怪:
list.stream()
.filter(string -> string.equalsIgnoreCase("5"))
.forEach(string -> removeMember(string));
Now change the List
type to LinkedList<>
. Last code with stream()
gives me a ConcurentModificationException
, and parallelStream()
suddenly works!
现在将List
类型更改为LinkedList<>
. 最后一个代码 withstream()
给了我一个ConcurentModificationException
,并且parallelStream()
突然起作用了!
So, the questions.
所以,问题。
Is the internal
parallelStream()
kitchen (Spliterators and other magic) smart enough to use such element removal forLinkedList
? Will it always work?Why was that NPE for
ArrayList
? Why NPE, notConcurentModificationException
I mean.
内部
parallelStream()
厨房(Spliterators 和其他魔法)是否足够聪明,可以使用这种元素去除LinkedList
?它会一直有效吗?为什么那个 NPE 是为了
ArrayList
?为什么是 NPE,不是ConcurentModificationException
我的意思。
回答by assylias
The behaviour of your code is essentially undefined (hence the various answers you get). The stream documentation(Non-Intereference section) states:
您的代码的行为基本上是未定义的(因此您会得到各种答案)。该流文件(非Intereference部分)规定:
Unless the stream source is concurrent, modifying a stream's data source during execution of a stream pipeline can cause exceptions, incorrect answers, or nonconformant behavior.
除非流源是并发的,否则在流管道执行期间修改流的数据源可能会导致异常、不正确的答案或不一致的行为。
And ArrayList
and LinkedList
are not concurrent.
并且ArrayList
和LinkedList
不是并发的。
You could use a concurrent source but it would make more sense to move away from modifying the source of the stream, for example by using Collection#removeIf
:
您可以使用并发源,但远离修改流的源会更有意义,例如使用Collection#removeIf
:
list.removeIf(string -> string.equalsIgnoreCase("5"));
回答by Eran
Adding some debug prints to the pipeline shows the source of the NullPointerException:
向管道添加一些调试打印显示 NullPointerException 的来源:
list.stream().peek(string -> System.out.println("peek1 " + string)).filter(string -> string.equalsIgnoreCase("5")).peek(string -> System.out.println("peek2 " + string)).forEach(string -> removeMember(string));
This outputs:
这输出:
peek1 0
peek1 1
peek1 2
peek1 3
peek1 4
peek1 5
peek2 5
peek1 7
peek1 8
peek1 9
peek1 null
Exception in thread "main" java.lang.NullPointerException
at HelloWorld.lambda$main(HelloWorld.java:22)
at HelloWorld$$Lambda/303563356.test(Unknown Source)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:174)
at java.util.stream.ReferencePipeline.accept(ReferencePipeline.java:373)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at HelloWorld.main(HelloWorld.java:22)
When "5" was removed from the List, all the elements from "6" to "9" were shifted one position to the left (i.e. their indices were decremented by 1). The Stream pipeline didn't detect it, so it skipped "6", and when it processed the last position (that originally contained "9"), it encountered null, which resulted in NullPointerException
when string.equalsIgnoreCase("5")
was evaluated for it.
当从列表中删除“5”时,从“6”到“9”的所有元素都向左移动了一个位置(即它们的索引减 1)。的Stream天然气管道没有检测到它,所以它跳过“6”,当它处理的最后一个位置(最初包含“9”),它遇到了空,这就造成了NullPointerException
当string.equalsIgnoreCase("5")
它进行了评价。
This is similar to what you'd get in this traditional for
loop:
这类似于您在这个传统for
循环中得到的结果:
int size = list.size();
for (int i = 0; i < size; i++) {
String string = list.get(i);
if (string.equalsIgnoreCase("5"))
removeMember(string);
}
Only here you'd get IndexOutOfBoundsException
instead of NullPointerException
, since list.get(i)
would fail when i==9
. I guess the Stream pipeline works directly on the internal array of the ArrayList
, so it doesn't detect that the size of the List has changed.
只有在这里你会得到IndexOutOfBoundsException
而不是NullPointerException
,因为list.get(i)
当 时会失败i==9
。我猜 Stream 管道直接在 的内部数组上工作ArrayList
,因此它不会检测到 List 的大小已更改。
EDIT:
编辑:
Following Holger's comment, I changed the code to eliminate the NullPointerException
(by changing the filter to filter(string -> "5".equalsIgnoreCase(string))
). This indeed produces ConcurrentModificationException
:
按照 Holger 的评论,我更改了代码以消除NullPointerException
(通过将过滤器更改为filter(string -> "5".equalsIgnoreCase(string))
)。这确实产生ConcurrentModificationException
:
peek1 0
peek1 1
peek1 2
peek1 3
peek1 4
peek1 5
peek2 5
peek1 7
peek1 8
peek1 9
peek1 null
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1380)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at HelloWorld.main(HelloWorld.java:22)
回答by Umberto Raimondi
If you want to use streams, instead of modifying the original collection (see immutability with its inherent thread-safety), you should just retrieve a new list without that element:
如果您想使用流,而不是修改原始集合(请参阅具有固有线程安全性的不变性),您应该只检索一个没有该元素的新列表:
list.stream().filter(string -> !string.equalsIgnoreCase("5"))
.collect(Collectors.toList());
Regarding your other question about parallelStream
and if that approach could always work?
关于您的其他问题parallelStream
以及该方法是否始终有效?
No, it will definitely not. The Lists
you are using are not built to support concurrent access, sometimes it will appear to work, other times it will fail as you saw or give you "unexpected" results. If you know that a data structure will be accessed by multiple thread always code accordingly.
不,绝对不会。在Lists
您使用不建,以支持并发访问,有时会出现工作,其他时候,你看到的还是给你“意外”的结果就会失败。如果您知道一个数据结构将被多个线程访问,则始终进行相应的编码。
回答by Binita Bharati
While using Java8 Lambdas, its best to not take the stack trace to its face value.The error should be read to understand that a NPE is being caused by some line of code within the forEach
lambda.So, you need to evaluate each line, and see what may be causing this.
在使用 Java8 Lambdas 时,最好不要将堆栈跟踪视为其表面价值。应该阅读错误以了解 NPE 是由forEach
lambda 中的某行代码引起的。因此,您需要评估每一行,并且看看是什么原因造成的。