当前位置: 首页 > 知识库问答 >
问题:

避免peek()和forEach()的侧面影响

慕云
2023-03-14

这是一个关于示例项目的基本问题,因为我仍在学习Java8个特性的最佳实践。

假设我有一个OrderDetail对象,该对象表示OrderDetail的List。同时,OrderDetail包含一个源代码和一个命运,以及数量产品

对于本例,我将把产品移动到命运,这两个都是产品仓库对象,具有availableStock属性,这将受到订单结果的影响。

现在,我需要更新所有源代码和destiny-ie的availableStock。库存应在destiny-ies增加,在sources减少OrderDetail数量。由于destinysource属于同一类型,我可以同时更新它们。问题是,当然,它们是不同的属性。

我的第一个想法是:

//Map keeps record of the sum of products moved from the source. Key is the id the Product
HashMap<Integer, Double> quantities;

ArrayList<OrderDetail> orderDetails;

/**
*  This could've been a single stream, but I divided it in two for readability here
*  If not divided, the forEach() method from ArrayList<> might been enough though
*/
public void updateStock() {
    List<ProductWarehouse> updated = new ArrayList<>();
    orderDetails.stream()
        .map(OrderDetail::getSource)
        .forEach(src -> {
            src.setAvailableStock(src.getAvailableStock - quantities.get(src.getProductId()));
            updated.add(src);
        });
    orderDetails.stream()
        .map(OrderDetail::getDestiny)
        .forEach(dst -> {
            dst.setAvailableStock(dst.getAvailableStock + quantities.get(dst.getProductId()));
            updated.add(dst);
        });
    productWarehouseRepository.save(updated);
}

虽然这行得通,但有一个问题:这就像Javadoc中关于“不必要的副作用”的例子。这并不完全相同,但考虑到它们的相似之处,我认为我在冒可以避免的风险。

我想到的另一个选择是使用peek(),它有自己的含义,因为根据javadoc,该方法主要被认为是一个调试工具。

HashMap<Integer, Double> quantities;
ArrayList<OrderDetail> orderDetails;

/**
* Either make it less readable and do one save or do one save for source and one for destiny
* I could also keep the updated list from before and addAll() the result of each .collect()
*/
public void updateStock() {
    productWarehouseRepository.save(
        orderDetails.stream()
            .map(/*Same as above*/)
            .peek(src -> {/*Same as above*/})
            .collect(toList()) //static import
    );
}

我也在一些博客文章中读到,应该避免peek。这也是因为文档说明了它的用法(主要是)。

因此,问题是:如果我想使用StreamAPI修改Collection的属性,那么遵循最佳实践的最佳方法是什么?对于这样的例子,使用Iterable.for每个()还是简单的增强for循环更好?老实说,我看不出像这样使用Stream的操作会产生什么副作用,但这可能是由于缺乏经验和对应用编程接口的理解。

这个问题中的代码只是为了举例,但是如果模型或更多信息更容易理解,我可以添加它。


共有2个答案

柯清野
2023-03-14

在这两种情况下,forEach都是一个终端操作,因此应该改用map

如果您想避免副作用,请在map中调用setter方法,然后将其收集到列表中,并将其添加到外部列表中。

updated.addAll(orderDetails.stream().map()...collect(Collectors.toList())

相关解决方案-如何将Java8流的元素添加到现有列表中

包翔
2023-03-14

您希望执行两个不同的操作。

>

  • 更新每个productWarehouse对象

    将所有ProductWarehouse对象收集到一个列表中,以便保存

    不要把这两种操作混在一起。

    public void updateStock() {
        orderDetails.forEach(o -> {
            ProductWarehouse src = o.getSource();
            src.setAvailableStock(src.getAvailableStock()-quantities.get(src.getProductId()));
            ProductWarehouse dst = o.getDestiny();
            dst.setAvailableStock(dst.getAvailableStock()+quantities.get(dst.getProductId()));
        });
        List<ProductWarehouse> updated = orderDetails.stream()
            .flatMap(o -> Stream.of(o.getSource(), o.getDestiny()))
            .collect(Collectors.toList());
        productWarehouseRepository.save(updated);
    }
    

    试图不惜一切代价在单个流操作中执行所有内容是没有意义的。在很少的情况下,迭代两次源是费用或不能保证产生相同的元素,您可以迭代结果列表,例如。

    public void updateStock() {
        List<ProductWarehouse> updated = orderDetails.stream()
            .flatMap(o -> Stream.of(o.getSource(), o.getDestiny()))
            .collect(Collectors.toList());
        for(int ix = 0; ix < updated.size(); ) {
            ProductWarehouse src = updated.get(ix++);
            src.setAvailableStock(src.getAvailableStock()-quantities.get(src.getProductId()));
            ProductWarehouse dst = updated.get(ix++);
            dst.setAvailableStock(dst.getAvailableStock()+quantities.get(dst.getProductId()));
        }
        productWarehouseRepository.save(updated);
    }
    

    第二次迭代绝对便宜。

  •  类似资料:
    • 问题内容: 我有以下组件会在上触发ESlint错误 。 如何在保持简洁语法和ESlint规则的同时避免警告? 我知道我可以添加注释以禁止显示警告,但是对每个组件执行此操作似乎都是多余且乏味的。 问题答案: 这里有四个选项: 1.禁用规则。 为什么? 这是避免ESLint错误的最简单方法。 为什么不? 使用no-shadow规则有助于防止使用时出现非常常见的错误。也就是说,尝试调用未连接的原始动作(

    • null本身不是对象,也不是Objcet的实例 问题: null代表不确定的对象, 是一个很模糊的概念, 容易产生二义性 Map.get(key)若返回value值为null,其代表的含义可能是该键指向的value值是null,亦或者该键在map中并不存在 优点: 从内存消耗和效率方面,null更加廉价 优化: Optional com.google.common.base.Optional Op

    • 我有一个包含许多JSON文件的文件夹,我希望使用JMeter将它们并行发送到Webservice。 我已经使用BeanShell采样器将文件收集到JMeter-variables中。然后我使用了一个ForEach控制器来执行HTTP请求(参见此线程)。 但问题是,当使用线程计数>1时,JSON文件会被多次处理。每个线程循环访问自己的JSON文件列表。 您知道多个线程如何共享ForEach控制器的输

    • 问题内容: 任何方法都可以在左右两侧(水平?)获得箱形阴影,而没有任何骇客或图像。我在用: 但它给周围的阴影。 我周围没有边界。 问题答案: 注意: 我建议您在下面查看@Hamish的答案;它不涉及此处描述的解决方案中不完善的“掩盖”。 您可以使用多个框阴影来接近;每一面一个 编辑 在顶部和底部的顶部再添加2个阴影,以遮盖流血的阴影。

    • 问题内容: 我有几个较旧的应用程序,它们在E_NOTICE错误级别上运行时会抛出很多“ xyz未定义”和“未定义偏移”消息,因为没有使用和明确检查变量的存在。 我正在考虑通过它们使它们与E_NOTICE兼容,因为有关丢失变量或偏移量的通知可能会节省生命,可能会获得一些较小的性能改进,并且总体而言,这是一种更清洁的方法。 但是,我不喜欢什么造成数百 和S ^确实给我的代码。它变得肿,可读性降低,而没

    • 嗨,我有很多雄辩的查询Foreach循环在我的应用程序,我试图避免这种做法可能。 示例: 我使用foreach获得我想要的东西的领域还有很多,其中一些我可能会查询10到100次——以下是我现在根据不同的堆栈溢出答案尝试的内容: 但这并没有给出我需要的数组格式,我需要一维[id]= 谢谢 非常感谢。尝试此操作会将主键作为数组键,将状态标题作为值,而不使用列名。抱歉,我刚刚意识到这个例子在foreac