Tony Piazza
Tony Piazza

Reputation: 335

Can I use a Java Stream Collector to implement this behavior

I have a small method containing the following code:

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()
             );
          });

Hopefully it's obvious that what I am doing is accumulating metrics while processing each Flight in the stream. The code does work but I'm wondering if there is a better (more functional) way to implement this behavior, possibly using a Collector. Any feedback is appreciated.

Thanks,

-Tony

Upvotes: 1

Views: 111

Answers (2)

janos
janos

Reputation: 124804

If the printing in the forEach is important, then your current solution is good as it is. forEach is designed for side effects, and you have two side effects: adding metrics to CarrierMetrics instance and printing.

If the printing in the forEach is only for debugging, and not intended in your final solution, then a more functional implementation would be to collect results directly into a CarrierMetrics instance, instead of initializing an instance first and manually adding with forEach. You can use the overload of collect(...) that takes 3 arguments:

  • A Supplier<CarrierMetrics> to create an initial CarrierMetrics instance, which will be used as an accumulator
  • A BiConsumer<CarrierMetrics, Flight> that pass a Flight instance to the accumulator
    • The type Flight is just a guess based on the code you shared. It's the type of the stream (and so the type of the parameter of CarrierMetrics.addFlight method)
  • A BiConsumer<CarrierMetrics, CarrierMetrics> that combines multiple accumulators in case of a parallel stream

Like this:

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

The third argument, the combiner, is a dummy, you will need to fix that. Its implementation should combine the two CarrierMetrics parameters into the first one. (I cannot give a concrete example, because you haven't shared enough details about CarrierMetrics to be able to see how to do. But to give some example, in case of a List accumulators, the implementation could be (a1, a2) -> a1.addAll(a2).)

(Lastly, this example assumes that CarrierMetrics has a parameterless constructor, for the CarrierMetrics::new reference to work. If there is no such constructor, you can use an appropriate lambda expression, such as () -> new CarrierMetrics(...).)

Upvotes: 5

Gal Shaboodi
Gal Shaboodi

Reputation: 764

If CarrierMetrics exposing a addAll(List<Flight> flights) method you can do the following:

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

Upvotes: 0

Related Questions