查看 Java 流的替代方案

mja*_*ard 3 java side-effects java-stream

peek关于Java Streams API有很多问题。我正在寻找一种使用 Java Streams 完成以下常见模式的方法。我可以让它与 Streams 一起工作,但它是不明显的,这意味着如果没有评论的话会有点危险,这并不理想。

boolean anyPricingComponentsChanged = false;
for (var pc : plan.getPricingComponents()) {
    if (pc.getValidTill() == null || pc.getValidTill().compareTo(dateNow) <= 0) {
        anyPricingComponentsChanged = true;
        pc.setValidTill(dateNow);
    }
}
Run Code Online (Sandbox Code Playgroud)

我的选择:

long numberChanged = plan.getPricingComponents()
    .stream()
    .filter(pc -> pc.getValidTill() == null || pc.getValidTill().compareTo(dateNow) <= 0)
    .peek(pc -> pc.setValidTill(dateNow))
    .count(); //`count` rather than `findAny` to ensure that `peek` processes all components.

boolean anyPricingComponentsChanged = numberChanged != 0;
Run Code Online (Sandbox Code Playgroud)

顺便说一句, whilecompareTo在这里并不是一个昂贵的操作,并且始终返回相同的结果,在其他情况下这可能不是真的,我宁愿避免为此模式多次运行它。

Ale*_*nko 7

// 确保peek处理所有组件

您无法真正确保peek()能够处理所有应该修改的流元素。在某些情况下,可以从管道中删除此操作,并且您不应通过 执行任何重要操作peek()。

这是文档peek()中的引用:

API注意事项:

此方法的存在主要是为了支持调试,您希望在元素流经管道中的某个点时查看它们......

如果流实现能够优化部分或全部元素的生成(例如使用 findFirst 等短路操作,或者在 count() 中描述的示例中),则不会为这些元素调用该操作。

另外,这是Stream API 文档中关于副作用的说明:

如果行为参数确实有副作用,除非明确说明,否则无法保证:

  • 这些副作用对其他线程的可见性;
  • 同一流管道中“同一”元素上的不同操作在同一线程中执行;和
  • 行为参数总是被调用,因为如果流实现可以证明它不会影响计算结果,那么它可以自由地从流管道中删除操作(或整个阶段)。

...

副作用的消失也可能令人惊讶。除了终端操作forEach和之外forEachOrdered,当流实现可以优化行为参数的执行而不影响计算结果时,行为参数的副作用可能并不总是被执行。(有关具体示例,请参阅有关计数操作的 API 注释。)

添加了 Amphesys

由于peek并不意味着对流执行的结果做出贡献,所以流实现可以随意丢弃它。

peek()您可以执行以下操作,而不是依赖:

List<PricingComponent> componentsToChange = plan.getPricingComponents()
    .stream()
    .filter(pc -> pc.getValidTill() == null || pc.getValidTill().compareTo(dateNow) <= 0)
    .toList();
    
componentsToChange.forEach(pc -> pc.setValidTill(dateNow));

boolean anyPricingComponentsChanged = componentsToChange.size() != 0;
Run Code Online (Sandbox Code Playgroud)

如果您不想将需要修改的对象具体化为列表,请坚持使用for- 循环。

笔记

  • 上面来自API 文档的引用,例如“如果可以证明流实现不会影响计算结果,则流实现可以自由地从流管道中删除操作(或整个阶段)”适用于具有嵌入式端的任何中间操作-影响。如果对结果没有影响,则可以消除副作用,或者优化整个管道阶段(流操作)。简而言之,副作用是函数除了产生所需结果之外所做的任何事情(例如i -> { side-effect; return i * 2; })

  • 尽管不建议分配peek()一个在任何情况下都应该执行的操作,但至少 choice 并不与 的语义相矛盾peek。相反,通过filter、map或其他并非旨在通过副作用进行操作的操作执行副作用不仅不能解决问题,而且还很奇怪,因为它违背了这些操作的语义并违反了原则最不令人惊讶的是。

  • @AndreyB.Panfilov 最重要的是*终端操作*。终端操作告诉 Stream,您对哪种结果感兴趣。作为副作用,在终端操作之外创建的任何其他“结果”都是不可靠的。如果您的终端操作是“.reduce(Integer::sum).orElse(0)”,则任何仍能产生正确总和的优化都是有效的。 (2认同)