首页 > 解决方案 > Java Streams - 在 map() 中使用 setter

问题描述

我与同事讨论过,我们不应该stream.map()像这里建议的解决方案那样在内部使用设置器 - https://stackoverflow.com/a/35377863/1552771

对此答案有一条评论不鼓励使用map这种方式,但没有给出理由说明为什么这是一个坏主意。有人可以提供一个可能的情况,为什么这会中断?

我已经看到一些讨论,人们谈论通过添加或删除项目来同时修改集合本身,但是使用map只是为数据对象设置一些值有什么负面影响吗?

标签: javacollectionsjava-8java-streamsetter

解决方案


在调用 setter 时使用副作用与将其用于非调试目的map有很多相似之处,在 Java 流中已经讨论过这些副作用真的只用于调试吗?peek

这个答案有一个很好的一般建议:

不要以非预期的方式使用 API,即使它实现了您的直接目标。这种方法将来可能会失效,未来的维护者也不清楚。

其他答案命名相关的实际问题;我必须引用自己:

您必须了解的重要一点是,流是由终端操作驱动的。终端操作确定是否必须处理所有元素或任何元素。

当您将具有副作用的操作放入map函数中时,您对将执行哪些元素甚至可能如何执行(例如以何种顺序)有一个特定的期望。期望是否会实现,取决于其他后续 Stream 操作,甚至可能取决于细微的实现细节。

举一些例子:

IntStream.range(0, 10) // outcome changes with Java 9
    .mapToObj(i -> System.out.append("side effect on "+i+"\n"))
    .count();
IntStream.range(0, 2) // outcome changes with Java 10 (or 8u222)
    .flatMap(i -> IntStream.range(i * 5, (i+1) * 5 ))
    .map(i -> { System.out.println("side effect on "+i); return i; })
    .anyMatch(i -> i > 3);
IntStream.range(0, 10) // outcome may change with every run
    .parallel()
    .map(i -> { System.out.println("side effect on "+i); return i; })
    .anyMatch(i -> i > 6);

此外,正如链接答案中已经提到的,即使您有一个处理所有元素并已排序的终端操作,也不能保证中间操作的处理顺序(或并行流的并发性)。

当你有一个没有重复的流和一个处理所有元素的终端操作和一个map只调用一个微不足道的 setter 的函数时,代码可能会碰巧做你想要的事情,但是代码对微妙的环境条件有太多的依赖关系,以至于它会变成维护的噩梦。这让我们回到了关于以非预期方式使用 API 的第一句话。


推荐阅读