Java 12+ solution
If you're on Java 12+:
carParts.collect(teeing(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add)),
Report::new
));
Assuming this static import:
import static java.util.stream.Collectors.*;
Solution using third party collectors
If a third party library is an option, which offers tuples and tuple collectors (e.g. jOOλ), you can do this even before Java 12
carParts.collect(Tuple.collectors(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add))
)).map(Report::new);
You can roll your own Tuple.collectors() if you want, of course, replacing Tuple2 by Map.Entry:
static <T, A1, A2, D1, D2> Collector<T, Tuple2<A1, A2>, Tuple2<D1, D2>> collectors(
Collector<? super T, A1, D1> collector1
, Collector<? super T, A2, D2> collector2
) {
return Collector.<T, Tuple2<A1, A2>, Tuple2<D1, D2>>of(
() -> tuple(
collector1.supplier().get()
, collector2.supplier().get()
),
(a, t) -> {
collector1.accumulator().accept(a.v1, t);
collector2.accumulator().accept(a.v2, t);
},
(a1, a2) -> tuple(
collector1.combiner().apply(a1.v1, a2.v1)
, collector2.combiner().apply(a1.v2, a2.v2)
),
a -> tuple(
collector1.finisher().apply(a.v1)
, collector2.finisher().apply(a.v2)
)
);
}
Disclaimer: I made jOOλ
Solution using Java 8 only and continuing with your attempts
You asked me in the comments to finish what you've started. I don't think that's the right way. The right way is to implement a collector (or use the suggested third party collector, I don't see why that wouldn't be an option) that does the same thing as the JDK 12 collector Collectors.teeing().
But here you go, this is one way how you could have completed what you've started:
carParts
// Stream<SimpleEntry<Part, BigDecimal>>
.map(x -> new AbstractMap.SimpleEntry<>(
new Part(x.name, x.supplier.name), x.price))
// Map<Integer, Report>
.collect(Collectors.toMap(
// A dummy map key. I don't really need it, I just want access to
// the Collectors.toMap()'s mergeFunction
e -> 1,
// A single entry report. This just shows that the intermediate
// step of creating a Map.Entry wasn't really useful anyway
e -> new Report(
Collections.singletonList(e.getKey()),
e.getValue()),
// Merging two intermediate reports
(r1, r2) -> {
List<Part> parts = new ArrayList<>();
parts.addAll(r1.parts);
parts.addAll(r2.parts);
return new Report(parts, r1.total.add(r2.total));
}
// We never needed the map.
)).get(1);
There are many other ways to do something similar. You could also use the Stream.collect(Supplier, BiConsumer, BiConsumer) overload to implement an ad-hoc collector, or use Collector.of() to create one.
But really. Use some variant of Collectors.teeing(). Or even an imperative loop, rather than the above.