【问题标题】:Can I use a Java Stream Collector to implement this behavior我可以使用 Java 流收集器来实现此行为吗
【发布时间】:2017-11-26 20:07:42
【问题描述】:

我有一个包含以下代码的小方法:

final int year = getYear();
final Carrier carrier = getCarrier();
final CarrierMetrics metrics = new CarrierMetrics(carrier);
repository.getFlightStream(year)
          .filter(flight -> flight.getCarrier().equals(carrier))
          .forEach(flight -> {
             metrics.addFlight(flight);
             printf("%,10d\t%,10d\t%,10d\t%,10d\r",
                    metrics.getTotalFlights(), 
                    metrics.getTotalCancelled(), 
                    metrics.getTotalDiverted(), 
                    metrics.getAirports().size()
             );
          });

希望很明显,我正在做的是在处理流中的每个航班时累积指标。该代码确实有效,但我想知道是否有更好(更实用)的方法来实现此行为,可能使用收集器。感谢您提供任何反馈。

谢谢,

-托尼

【问题讨论】:

  • 使用Collector.of 定义一个自定义收集器。
  • 您的 peek().allMatch() 是一种非常复杂的 forEach() 方式。
  • 是的,这就是收集器的工作。但是你也可以使用这个 collect() 方法:docs.oracle.com/javase/8/docs/api/java/util/stream/…
  • 同意关于使用 forEach 而不是 peek 的评论。代码相应更新。

标签: java java-stream collectors


【解决方案1】:

如果forEach中的打印很重要, 那么您当前的解决方案就很好。 forEach 专为副作用而设计, 并且您有两个副作用:将指标添加到 CarrierMetrics 实例打印。

如果forEach中的打印只是为了调试, 并且不打算用于您的最终解决方案, 那么更实用的实现是将结果直接收集到CarrierMetrics 实例中, 而不是先初始化实例并使用forEach 手动添加。 您可以使用带有 3 个参数的 collect(...) 的重载:

  • Supplier<CarrierMetrics> 用于创建初始 CarrierMetrics 实例,该实例将用作累加器
  • Flight 实例传递给累加器BiConsumer<CarrierMetrics, Flight>
    • Flight 类型只是基于您共享的代码的猜测。它是流的类型(也是CarrierMetrics.addFlight方法的参数类型)
  • BiConsumer<CarrierMetrics, CarrierMetrics> 在并行流的情况下组合多个累加器

像这样:

final int year = getYear();
final CarrierMetrics metrics = repository.getFlightStream(year)
      .filter(flight -> flight.getCarrier().equals(carrier))
      .collect(CarrierMetrics::new, CarrierMetrics::addFlight, (a1, a2) -> {});

第三个参数,combiner,是一个哑元, 你需要解决这个问题。 它的实现应该将两个CarrierMetrics 参数组合到第一个参数中。 (我不能举一个具体的例子,因为你没有分享足够的关于CarrierMetrics 的细节来了解如何做。 但是举个一些的例子,如果是List 累加器, 实现可以是(a1, a2) -> a1.addAll(a2)。)

(最后,此示例假定CarrierMetrics 具有无参数构造函数,以便CarrierMetrics::new 引用起作用。 如果没有这样的构造函数,可以使用合适的 lambda 表达式,比如() -> new CarrierMetrics(...)。)

【讨论】:

  • 你知道组合器对于顺序流来说不是可选的,对吧?
  • @janos 这与我计划做的类似,只是在我的情况下,CarrierMetrics 的构造函数需要一个参数。感谢您提供有关在此处使用 lambda 表达式的提示。
  • @shmosel 为什么你认为它不是可选的? AFAIK,即使对于顺序流,也允许流使用它,但实际上,AFAIK,它没有,我不明白为什么会这样。为了正确起见,我仍然会实现它,以防此代码变得并行或被复制并粘贴到另一个用例。
  • @JBNizet 这不是可选的,除非它被记录为可选。仅仅因为它有效并不能使它正确。
  • @JBNizet combiner 确实目前仅用于并行流;但没有规范说它不会用于任何其他流操作:并行或顺序。
【解决方案2】:

如果CarrierMetrics 暴露了addAll(List<Flight> flights) 方法,您可以执行以下操作:

List<Flight> flights = repository.getFlightStream(year)
                    .filter(flight -> flight.getCarrier().equals(carrier))
                    .collect(Collectors.toList());
metrics.addAll(flights);

【讨论】:

  • 这种特殊的方法是在指标更新时打印指标,所以等到它们全部累积起来似乎没有意义。
  • @TonyPiazza 你的意思是你真的想在收集时打印指标的每一次演变?那不只是为了调试吗?为什么要这样做?
  • 我的想法和@JBNizet 完全一样
  • @Gal 说,将所有航班收集到内存中的列表是浪费的,需要两次通过,并且不允许并行化度量计算。收集器是一个更好的解决方案。即使是 OP 的原始解决方案也是更好的解决方案。
  • 您正在使用哪种类型的流源,速度如此之慢,以至于您甚至有时间查看中间结果?
猜你喜欢
  • 2012-06-07
  • 1970-01-01
  • 1970-01-01
  • 2014-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
  • 1970-01-01
相关资源
最近更新 更多