Java流无干扰和副作用

时间:2018-10-22 13:28:19

标签: java java-stream

查看Java java.util.stream软件包文档时,对于流使用中遵循的最佳实践产生了疑问。 考虑以下代码:

HashMap<Integer,Integer> map = new HashMap<>();
map.put(1,1);
map.put(2,2);
map.put(3,3);
map.put(4,4);
map.keySet().parallelStream().forEach(key -> {
        if (key == 3) { 
            map.put(3,0);
        }
});
  1. 代码输出是否始终等于([1,1],[2,2],[3,0],[4,4])?
  2. 能否将map.put(3,0)视为无干扰操作?
  3. 能否将map.put(3,0)视为可吸收的副作用?

换句话说,以上代码可以被视为符合流文档中建议的最佳实践吗?

3 个答案:

答案 0 :(得分:6)

您的示例绝对违反了non interference requirement

  

对于大多数数据源,防止干扰意味着确保在流管道执行期间根本不修改数据源。值得注意的例外是流的源是并发集合,这些流是专门为处理并发修改而设计的。

您的数据源HashMap不能处理并发修改,因此,在执行流管道时完全不应对其进行修改。

因此,您的第二个和第三个问题的答案是否定的。

对于第一个问题,由于您的条件确保只有一个线程将调用map.put(3,0),因此您的特定代码仍可能产生预期的结果。但是,这仍然被认为是Stream的不正确用法。

答案 1 :(得分:1)

不,不,不。
避免副作用。
符合文档的示例代码:

Map<Integer,Integer> updatedMap = map.keySet().parallelStream()
        .filter(e -> e == 3)
        .collect(Collectors.toMap(Function.identity(), e -> 0));
map.putAll(updatedMap);

答案 2 :(得分:1)

比较(a)

map.keySet().parallelStream().forEach(key -> {
        if (key == 3) { 
            map.put(3, 0);
        }
});

(添加了一个新条目)

带有(b)

map.entrySet().parallelStream().forEach(e -> {
        if (e.getKey() == 3) { 
            e.setValue(0);
        }
});

(没有创建,移动Entry对象。但是请注意LinkedHashMap。)

  • (a)不安全
  • (b)安全

    1. 代码输出是否始终等于([1,1],[2,2],[3,0],[4,4])?

      (a)否(b)是

    2. map.put(3, 0)是否可以视为无干扰操作?

      (a)否(b)setValue(0)

    3. map.put(3, 0)是否可以视为可接受的副作用?

      (a)否(b)setValue(0)

所以(a)是邪恶的,(b)是好的。

为什么要提到entrySet.setValue?

实际上 HashMap.put在Oracle 实现中可能与Entry.setValue相同。那将需要使用实施知识-很难。

鉴于Entry.setValue基于原始地图的支持,因此可能会推断出只有value字段被覆盖。请注意,LinkedHashMap需要对条目进行重新排序,并且重新排序也不安全。