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

我有一个类CarPart定义为:

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

一个类Report

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

和一个班级Part

class Part {
  String name;
  String supplierName;
}

给定 a 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(.....)

我处理它完全错误吗?我试图只迭代一次流。

PS:假设所有的 getter 和 setter 都可用。

回答

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()如果你愿意,你可以自己推出,当然,替换Tuple2Map.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(). 甚至是命令式循环,而不是上面的。


以上是将流映射到另一个对象的实例的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>