【问题标题】:How to avoid nested forEach calls?如何避免嵌套的 forEach 调用?
【发布时间】:2019-05-18 07:36:22
【问题描述】:

我有以下代码:

interface Device {
    // ...
    boolean isDisconnected();
    void reconnect();
}

interface Gateway {
    // ...
    List<Device> getDevices();
}

...

for (Gateway gateway : gateways) {
    for(Device device : gateway.getDevices()){
        if(device.isDisconnected()){
            device.reconnect();
        }
    }
}

我想使用 Stream API 重构代码。我的第一次尝试如下:

gateways
    .stream()
    .forEach(
        gateway -> {
            gateway
                .getDevices()
                .parallelStream()
                .filter(device -> device.isDisconnected())
                .forEach(device -> device.reconnect())
            ;
        }
    )
;

我不喜欢它,所以经过一些修改后,我最终得到了以下代码:

gateways
    .parallelStream()
    .map(gateway -> gateway.getDevices().parallelStream())
    .map(stream -> stream.filter(device -> device.isDisconnected()))
    .forEach(stream -> stream.forEach(device -> device.reconnect()))
;

我的问题是是否有办法避免嵌套forEach

【问题讨论】:

    标签: java collections foreach java-8 java-stream


    【解决方案1】:

    您应该使用flatMap 而不是map 来展平流:

    gateways
        .parallelStream()
        .flatMap(gateway -> gateway.getDevices().parallelStream())
        .filter(device -> device.isDisconnected())
        .forEach(device -> device.reconnect());
    

    我会通过使用方法引用而不是 lambda 表达式来进一步改进它:

    gateways
        .parallelStream()
        .map(Gateway::getDevices)
        .flatMap(List::stream)
        .filter(Device::isDisconnected)
        .forEach(Device::reconnect);
    

    【讨论】:

    • 谢谢,它有效! method references 的代码和第一个的性能一样吗?它看起来很优雅。
    • 是的,确实如此。请阅读this post了解更详细的说明。
    【解决方案2】:

    在使用并行流之前,我会尝试使用顺序流:

    gateways
        .stream()
        .flatMap(gateway -> gateway.getDevices().stream())
        .filter(device -> device.isDisconnected())
        .forEach(device ->  device.reconnect())
    ;
    

    这个想法是通过gateways.stream() 创建一个流,然后通过flatMap 展平从gateway.getDevices() 返回的序列。

    然后我们应用filter 操作,其作用类似于代码中的if 语句,最后,forEach 终端操作使我们能够在每个通过过滤操作的设备上调用reconnect

    Should I always use a parallel stream when possible?

    【讨论】:

      【解决方案3】:

      不要将您的代码重构为使用 Streams。与这样做相比,您不会获得任何好处,也不会获得任何优势,因为代码现在对于未来的维护者来说可读性和惯用性较差。

      通过不使用流,可以避免嵌套 forEach 语句。

      请记住:流意味着没有副作用,以实现更安全的并行化。 forEach 根据定义引入了副作用。您失去了流的好处并同时失去了可读性,这使得这样做根本不受欢迎。

      【讨论】:

      • 感谢您的回答。这只是我的家庭项目。我正在尝试通过做一些伪真实的任务来学习 Stream API。
      • 了解 Stream API 的一种方法是知道何时应用它。这感觉就像是其中一次,特别是因为您不能保证您在流中执行的操作是无副作用的,这将使并行化安全
      • 我认为上面 ETO 的回答是 flatMap,流版本比非流版本更漂亮、更干净。
      • @MateenUlhaq:这与编写“漂亮”代码无关。这是关于惯用代码。流具有并行化的能力。如果您使用forEach 或任何具有副作用的终止操作数,则并行化代码的能力显着减弱。如果您想并行执行此操作,您需要一个不同的结构来确保围绕Device#reconnect 的操作实际上是线程安全的。这样做并不能给你那种保证。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-05
      相关资源
      最近更新 更多