将流映射到另一个对象的实例

Mapping a stream to an instance of another object

我有一个 class CarPart 定义为:

class CarPart {
  String name;
  BigDecimal price;
  Supplier supplier;
}

A class Report:

class Report {
   List<Part> parts;
   BigDecimal total;
}

和一个class Part:

class Part {
  String name;
  String supplierName;
}

给定一个 Stream<CarPart> carParts,我需要创建一个 Report 对象。

我的想法是创建一个 Map<List<Part>, BigDecimal>,其中 List<Part> 是转换后的 CarPart 对象的列表,BigDecimal 是总和给定流中所有汽车零件的价格。之后,我可以访问包含单个条目的 Map<>,并且可以创建一个新的 Report.

我开始这样做了,但我不确定如何收集它。在下面我做的 .map 之后,我实际上有一个 Map<Part, BigDecimal> 但我需要总结列表中的所有 Part 对象,并添加所有 BigDecimal 来创建Report.

的总价值
   carParts.stream()
           .map(x -> {
               return new AbstractMap.SimpleEntry<>(new Part(x.getName(), x.supplier.getName()), x.getPrice());
           })
           .collect(.....)

我是不是处理的完全错误?我试图只迭代一次流。

P.S:假设所有的getters和setters都可用。

Java 12+ 解

如果您 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
));

假设这个静态导入:

import static java.util.stream.Collectors.*;

使用第三方收集器的解决方案

如果可以选择提供元组和元组收集器的第三方库(例如 jOOλ),您甚至可以在 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);

如果你愿意,你可以自己滚动 Tuple.collectors(),当然,将 Tuple2 替换为 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)
        )
    );
}

免责声明:我制作了 jOOλ

仅使用 Java 8 的解决方案并继续您的尝试

你在评论中要求我完成你已经开始的工作。我不认为这是正确的方法。正确的方法是实现一个与 JDK 12 收集器 Collectors.teeing() 做同样事情的收集器(或使用建议的第三方收集器,我不明白为什么这不是一个选项) .

但现在开始,这是您完成已开始工作的一种方式:

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

还有很多其他方法可以做类似的事情。您还可以使用 Stream.collect(Supplier, BiConsumer, BiConsumer) 重载来实现临时收集器,或使用 Collector.of() 创建一个。

但真的。使用 Collectors.teeing() 的一些变体。甚至是命令式循环,而不是上面的循环。

带有可变 Report class

Report class 是可变的并且您有修改它的必要权限时,您可以使用

Report report = carParts.stream()
   .collect(
        () -> new Report(new ArrayList<>(), BigDecimal.ZERO),
        (r, cp) -> {
            r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
            r.total = r.total.add(cp.getPrice());
        },
        (r1, r2) -> { r1.parts.addAll(r2.parts); r1.total = r1.total.add(r2.total); });

具有不可变的 Report class

当你不能修改Report个实例时,你必须使用一个临时的可变对象进行处理,然后创建一个最终的结果对象。否则,操作类似:

Report report = carParts.stream()
   .collect(Collector.of(
        () -> new Object() {
            List<Part> parts = new ArrayList<>();
            BigDecimal total = BigDecimal.ZERO;
        },
        (r, cp) -> {
            r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
            r.total = r.total.add(cp.getPrice());
        },
        (r1, r2) -> {
            r1.parts.addAll(r2.parts);
            r1.total = r1.total.add(r2.total);
            return r1;
        },
        tmp -> new Report(tmp.parts, tmp.total)));

好吧,原则上,您不需要可变对象,但可以将操作实现为纯归约,但是 Mutable Reduction 又名 collect 操作对于此特定目的(即将值收集到 List).