我编写了以下代码来测试修改流的后备集合的副作用
List<Integer> x = new ArrayList<>(Arrays.asList(1, 11, 21));
x.stream().filter(i -> {
if (i < 10) {
x.remove(i);
return false;
}
return true;
}).forEach(System.out::println);
输出将为
21
Exception in thread "main" java.lang.NullPointerException
谁能告诉我这是怎么回事?特别是NullPointerException来自哪里?谢谢。
答案 0 :(得分:8)
您通过传递干扰谓词来违反contract of filter
。有关无干扰要求的详细说明,请参见here。具体说:
无干扰的需求适用于所有管道,而不仅仅是并行管道。除非流源是并发的,否则在流管道执行期间修改流的数据源可能导致异常,错误答案或不符合要求的行为。
因此,对您的问题的答案是,由于某些未指定的实现细节,正在运行的Java版本以该特定方式以该特定数据失败。不同版本的Java可能会以其他方式失败,或者不抛出任何东西而产生您期望的答案或产生错误的答案。
您可以使用指定用于产生CONCURRENT分隔符的Collection
进行尝试:
Collection<Integer> x = new ConcurrentLinkedQueue<>(Arrays.asList(1, 11, 21));
它应该执行您期望的操作,并且不会引发任何异常。
答案 1 :(得分:6)
要了解它,就让我们为发生的事情制定一个模式:
您的列表如下:
+---+ +---+ +---+
| 1 | --> |11 | --> |21 |
+---+ +---+ +---+
0 1 2
现在过滤器开始工作了,它将按索引遍历列表
第一次迭代(索引= 0,i = 1 )
检查i < 10
->是,然后将其从列表中删除。现在看起来像这样:
+---+ +---+ +-----+
|11 | --> |21 | --> |null |
+---+ +---+ +-----+
0 1 2
第二次迭代(索引= 1,i = 21 而不是11)
检查i < 10
->是,然后将其从列表中删除。现在看起来像这样:
+---+ +-----+ +-----+
|21 | --> |null | --> |null |
+---+ +-----+ +-----+
0 1 2
第三次迭代(索引= 2,i = 空)
检查i < 10
是否表示null < 10
->这将抛出NullPointerException
。
现在的问题是,为什么要这么做?您只需要:
x.removeIf(i -> i < 10);
x.forEach(System.out::println);