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

stu*_*ent 5 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);
Run Code Online (Sandbox Code Playgroud)

输出将是

21
Exception in thread "main" java.lang.NullPointerException
Run Code Online (Sandbox Code Playgroud)

谁能告诉我这里到底发生了什么?特别是,NullPointerException来自哪里?谢谢.

Mis*_*sha 8

您通过传递干扰谓词违反了合同filter.有关非干扰要求的详细说明,请参见此处.它具体说:

不干涉的需要适用于所有管道,而不仅仅是并行管道.除非流源是并发的,否则在执行流管道期间修改流的数据源可能会导致异常,错误答案或不一致的行为.

因此,您的问题的答案是,由于某些未指定的实现细节,您运行的java版本以特定方式使用该特定数据失败.不同版本的java可能会以某种其他方式失败,或者不抛出任何内容并产生您期望的答案或产生错误的答案.

您可以使用Collection指定生成CONCURRENT spliterator的方法尝试:

Collection<Integer> x = new ConcurrentLinkedQueue<>(Arrays.asList(1, 11, 21));
Run Code Online (Sandbox Code Playgroud)

它应该做你期望的,而不是抛出任何异常.


YCF*_*F_L 6

要了解它,我们可以制作一个关于完全发生的模式:

你的清单看起来像这样:

+---+       +---+       +---+      
| 1 |  -->  |11 |  -->  |21 |
+---+       +---+       +---+
  0           1           2
Run Code Online (Sandbox Code Playgroud)

现在过滤器的工作将按索引迭代列表索引

第一次迭代(索引= 0,i = 1)

检查是否i < 10- >是然后从列表中删除它.现在看起来像这样:

+---+       +---+       +-----+      
|11 |  -->  |21 |  -->  |null |
+---+       +---+       +-----+
  0           1           2
Run Code Online (Sandbox Code Playgroud)

第二次迭代(索引= 1,i = 21不是11)

检查是否i < 10- >是然后从列表中删除它.现在看起来像这样:

+---+       +-----+       +-----+      
|21 |  -->  |null |  -->  |null |
+---+       +-----+       +-----+
  0            1             2
Run Code Online (Sandbox Code Playgroud)

第三次迭代(index = 2,i = null)

检查是否i < 10意味着null < 10- >这将抛出NullPointerException.


现在的问题是,你为什么这样做?所有你需要的只是:

x.removeIf(i -> i < 10);
x.forEach(System.out::println);
Run Code Online (Sandbox Code Playgroud)