【问题标题】:Mapping a stream to an instance of another object将流映射到另一个对象的实例
【发布时间】:2021-05-25 19:40:45
【问题描述】:

我有一个类 CarPart 定义为:

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

A类Report

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

还有一个班级Part

class Part {
  String name;
  String supplierName;
}

给定一个Stream&lt;CarPart&gt; carParts,我需要创建一个Report 对象。

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

我开始这样做了,但我不知道如何收集它。在我在下面做的.map 之后,我实际上有一个Map&lt;Part, BigDecimal&gt;,但我需要在一个列表中汇总所有Part 对象,并添加所有BigDecimal 以创建Report 的总值.

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

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

P.S:假设所有的 getter 和 setter 都可用。

【问题讨论】:

    标签: java java-8 java-stream collectors


    【解决方案1】:

    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(),当然,用Map.Entry替换Tuple2

    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 并继续尝试的解决方案

    你在 cmets 中要求我完成你已经开始的事情。我不认为这是正确的方法。正确的方法是实现一个与 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() 的一些变体。甚至是命令式循环,而不是上面的。

    【讨论】:

    • 一个更简单的Java-8兼容teeing实现可以在this answer.找到
    • 谢谢@LukasEder!不幸的是,我在 Java 8 上运行。如果没有第三方库,我怎么能做到这一点?
    • @maria82:您复制粘贴collectors() 方法并根据您的需要调整它,或点击@Naman 的链接
    • @maria82:不,这不是完全错误,只是感觉更费力。无论您使用的是 Java 12、第三方还是您自己的,在您的代码库中拥有这样一个 teeing 实用程序都会一次又一次地有用。
    • 您的.collect(Collectors.toMap(e -&gt; 1, …)).get(1) 是伪装的reduce。将.map(x -&gt; new AbstractMap.SimpleEntry&lt;&gt;(new Part(x.name, x.supplier.name), x.price))e -&gt; new Report(Collections.singletonList(e.getKey()), e.getValue()) 函数组合成一个.map(x -&gt; new Report(Collections.singletonList(new Part(x.name, x.supplier.name)), x.price))。然后,使用合并函数作为归约函数,例如.reduce( /* the merge function without any changes */).orElseGet(() -&gt; new Report(Collections.emptyList(), BigDecimal.ZERO)).
    【解决方案2】:

    具有可变的Report

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

    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

    当你不能修改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 操作对于此特定目的更有效(即,将值收集到 @ 987654329@).

    【讨论】:

      猜你喜欢
      • 2013-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-10
      • 1970-01-01
      • 1970-01-01
      • 2020-05-07
      相关资源
      最近更新 更多