【问题标题】:What is the best practice for managing Java 8 streams with multiple results管理具有多个结果的 Java 8 流的最佳实践是什么
【发布时间】:2018-08-04 15:28:51
【问题描述】:

当我想从迭代中获得多个结果时,如何使用流处理 Java 8 中的 forEach。当然,在流 forEach 操作中调用 List.add 方法不是一种选择……您将如何使用 Java 8 流重写此代码?

additionalWebsitesList = Lists.newArrayList();
String additionalWebistesCsv = "";
for (String website : additionalWebsites) {
    WebsiteModel websiteModel = getWebsiteModel(website);

    //How this can be processed via Stream???
    additionalWebsitesList.add(websiteModel);
    areaModels.addAll(websiteModel.getAreas());
    additionalWebistesCsv += websiteModel.getId();
}

【问题讨论】:

  • 为什么要这样做,保持代码不变。
  • 我不会。 areaModels 是什么?

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


【解决方案1】:

使用流执行此类操作的方法是使用自定义收集器。假设我们定义了以下类:

public class Results {
    private final List<WebsiteModel> additionalWebsitesList = new ArrayList<>();
    private final List<AreaModel> areaModels = new ArrayList<>();
    private final StringBuilder additionalWebsitesCsv = new StringBuilder();

    public void accumulate(WebsiteModel websiteModel) {
        additionalWebsitesList.add(websiteModel);
        areaModels.addAll(websiteModel.getAreas());
        additionalWebsitesCsv.append(websiteModel.getId());
    }

    public void combine(Results another) {
        additionalWebsitesList.addAll(another.additionalWebsitesList);
        areaModels.addAll(another.areaModels);
        additionalWebsitesCsv.append(another.additionalWebsitesCsv);
    }

    public List<WebsiteModel> getAdditionalWebsitesList() {
        return additionalWebsitesList;
    }

    public List<AreaModel> getAreaModels() {
        return areaModels;
    }

    public String getAdditionalWebsitesCsv() {
        return additionalWebsitesCsv.toString();
    }
}

有了这个Results 类,您就可以收集结果了:

Results results = additionalWebsites.stream()
    .map(this::getWebsiteModel)
    .collect(Results::new, Results::accumulate, Results::combine);

这使用Stream.collect 方法。第一个参数是Supplier,它创建可变对象,流的元素将通过第二个参数中提供的累加器累积到该对象中。第三个参数是一个合并,如果流是并行的,它将只用于合并部分结果。

【讨论】:

  • 在我看来,这确实是低效的代码。您实际上只需要一个 Result 对象,但在组合器中您必须始终传递一个新对象。实际上这段代码的性能应该很差。 @federico 你对此有何看法
  • @VinayPrajapati 按照合同规定,您必须提供一个组合器,即使对于顺序流也是如此,尽管在这种情况下它可能是一个虚拟组合器或引发异常的组合器。如果流是并行的,流 API 会自动将元素拆分为块并并行处理每个块,因此必须有一种方法将结果组合在一起。相反,对于非常多的元素,值得拆分、处理和组合。对于少数元素,不值得安装所有必需的基础架构来并行处理流管道。
【解决方案2】:

管理多个 Java 8 流的最佳实践是什么? 结果

当然在流forEach操作中调用List.add方法不是 一个选项。

最佳做法不是强迫您使用流,因为用例不合适。
Stream 接口 javadoc 将自己描述为:

支持顺序和并行聚合的元素序列 操作。

Stream 操作返回自己的类型元素序列。
所以Streamcollects 最终会根据元素序列的类型返回一种单一的东西:单一或集合。
例如,我们可以收集 FooListFooMapFooInteger 键索引,等等...
Stream 收集不是设计的收集不同的自定义事物,例如 FooList + BarList + Bar 的计数 + Foo

正如@Eugene 强调的那样,Streams 还提供了一种获取IntSummaryStatistics 的方法,IntSummaryStatistics 是一组事物:最小、最大、总和和平均值的公共聚合。 但是我们不应该认为它最终收集了一个东西:统计数据吗?

所以你有三种方法:

  • Stream 放在一边以供您的用例使用,并保留for 循环。

  • 使用多个流:您要收集的自定义事物的一个流。

  • 使用带有自定义收集器的单个流。

在任何情况下我都不会使用第二种方式,因为它会产生比实际代码更不可读和更直接的代码。

关于 Federico Peralta Schaffner 的回答说明的最后一种方式(自定义收集器)。它更直,并且可以从流并行中受益。所以这是一个值得考虑的选择。
但它也需要更多的样板代码,并且需要更多的间接阅读来理解实际逻辑。
所以我认为我只会在两种情况下引入自定义收集器:

  • 收集器被重复使用。
  • 我们希望从流并行中受益并且我们有大量的元素需要处理(Federico Peralta Schaffner 在其comment 中对此进行了很好的解释,谢谢)。

在任何其他情况下,我都会保留for 循环。

【讨论】:

  • 谢谢,这就是我要找的答案。
  • @davidxxx IntSummaryStatistics 一次做不止一件事,这没有错。自定义收集器在这里非常好
  • @Eugene 是的,但我们能不能认为收集统计数据得到一件事:统计数据?此外,它是一个标准并且开箱即用。 OP 想要收集自定义类的多个实例。我认为这是一个不同的要求。关于创建自定义收集器,我不同意。使用这种方式的 Federico Peralta Schaffner 答案的代码很有趣,但需要更多样板代码来执行 OP 的原始代码以更直接的方式执行的操作。我会使用它,但无论如何都不会。我更新了我的答案。
  • @davidxxx 正是 OP 所要求的,通过 java-8 完成同样的事情,一次通过,多个结果 == 自定义收集器,另一个答案已经表明。你是对的,无论 OP 有什么好的,只是 is 有一种方法可以在 java-8 中做到这一点......
  • @Federico Peralta Schaffner 我明白你的意思。总而言之,理论上它可能会更快,但实际上合适的用例很少见。感谢您的有趣反馈!
【解决方案3】:

作为 Java 8 流的代码:

additionalWebsitesList = additionalWebsites.stream()
        .map(this::getWebsiteModel)
        .collect(Collectors.toList());
areaModels = additionalWebsites.stream()
        .flatMap(w -> getWebsiteModel(w).getAreas().stream())
        .collect(Collectors.toList());
String additionalWebistesCsv = additionalWebsites.stream()
        .map(this::getWebsiteModel)
        .map(WebsiteModel::getId)
        .collect(Collectors.joining());

【讨论】:

  • 正是我的想法,我的答案已经完成了一半;)。但最好声明您避免在数据集上一次执行逻辑以避免副作用,因此不想与它并行。
  • 是的,但这意味着您要遍历列表 3 次?对吗?
  • @Rado 那又怎样?迭代本身不需要时间。问题是如果getWebsiteModel() 很慢,那么调用它三次是不好的。但是作为一个简单的 getter,这也不需要时间,所以做 3 次也没有坏处。
  • 这段代码只是一个例子。我很好奇是否有一些常规的方法可以使用流来编写它,看起来没有;)。非常感谢您的反应...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
相关资源
最近更新 更多