【问题标题】:Steam operation modifying the actual listSteam操作修改实际列表
【发布时间】:2019-05-02 18:04:03
【问题描述】:

我在某处读到流操作总是在终端操作时返回一个新集合,并且不会更改已应用流操作的原始集合。

但在我的情况下,原始列表已被修改。

 return subscriptions.stream()
            .filter(alertPrefSubscriptionsBO -> (alertPrefSubscriptionsBO.getType() == AlertPrefContactTypeEnum.PRIMARY_CONTACT || alertPrefSubscriptionsBO.getType() == AlertPrefContactTypeEnum.SECONDARY_CONTACT))
            .map(alertPrefSubscriptionsBO -> {
                if (alertPrefSubscriptionsBO.getType() == AlertPrefContactTypeEnum.PRIMARY_CONTACT) {
                    alertPrefSubscriptionsBO.setType(AlertPrefContactTypeEnum.PRIMARY);
                } else
                    alertPrefSubscriptionsBO.setType(AlertPrefContactTypeEnum.SECONDARY);

                return alertPrefSubscriptionsBO;
            })
            .collect(groupingBy(AlertPrefSubscriptionsBO::isActiveStatus, groupingBy(AlertPrefSubscriptionsBO::getAlertLabel, Collectors.mapping((AlertPrefSubscriptionsBO o) -> o.getType()
                    .getContactId(), toSet())
            )));

修改此操作后,订阅列表仅包含 AlertPrefContactTypeEnum.PRIMARY 和 AlertPrefContactTypeEnum.SECONDARY 对象。我的意思是列表的大小保持不变,但值发生了变化。

【问题讨论】:

  • 是的。但是您不应该这样做,因为流 API 不是为此目的而设计的。但是,Java 没有一种语言机制来阻止你
  • 请注意,此代码不会修改原始集合:它仍然包含相同的对象。它修改了什么集合中包含的对象的状态,这不是一回事。

标签: java java-stream


【解决方案1】:

那是因为你违反了map(Function<? super T,? extends R> mapper)方法的约定:

参数:
mapper - 一个 non-interfering, stateless 函数应用于每个元素

你违反了“无状态”部分:

无状态行为

如果流操作的行为参数是有状态的,流管道结果可能是不确定的或不正确的。有状态的 lambda(或实现适当功能接口的其他对象)的结果取决于在流管道执行期间可能发生变化的任何状态。有状态 lambda 的一个示例是 map() 的参数:

Set<Integer> seen = Collections.synchronizedSet(new HashSet<>());
stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...

在这里,如果映射操作是并行执行的,由于线程调度的差异,相同输入的结果可能会因运行不同而不同,而对于无状态 lambda 表达式,结果将始终相同。

另请注意,尝试从行为参数访问可变状态会使您在安全性和性能方面做出错误的选择;如果您不同步对该状态的访问,您就会遇到数据竞争,因此您的代码会被破坏,但如果您确实同步对该状态的访问,您可能会面临竞争破坏您正在寻求从中受益的并行性的风险。最好的方法是避免有状态的行为参数完全流式操作;通常有一种方法可以重组流管道以避免有状态。

实现该映射操作的正确方法是复制alertPrefSubscriptionsBO 并给copy一个新类型。

遵循java.time 类使用的样式,例如查看ZonedDateTime 的所有withXxx(...) 方法,您可以将alertPrefSubscriptionsBO 对象制作或视为不可变对象,并拥有获取属性更改的副本的方法,例如在类上使用方法 withType(...) 并使用 AlertPrefContactTypeEnum 枚举的静态导入,您的代码可以是:

.map(bo -> bo.withType(bo.getType() == PRIMARY_CONTACT ? PRIMARY : SECONDARY))

【讨论】:

    猜你喜欢
    • 2014-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-04
    • 1970-01-01
    • 2015-06-10
    相关资源
    最近更新 更多