了解修改流的后备集合的副作用

时间:2018-06-20 21:35:53

标签: java java-8 java-stream

我编写了以下代码来测试修改流的后备集合的副作用

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来自哪里?谢谢。

2 个答案:

答案 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);