【问题标题】:Grouping values in Hash Map in Java Stream using custom Collector object?使用自定义收集器对象对 Java Stream 中的哈希映射中的值进行分组?
【发布时间】:2020-04-29 17:05:30
【问题描述】:

Java Stream 新手在这里。目前,我正在通过 book 完成有关收集器的第 6 章(使用流收集数据)。

我的对象看起来像这样。

    public class Report {

    private String movie;
    private int movieId;
    private int projections;
    private int tickets;
    private double income;
}

我们的想法是获得某种一般性的总结报告。基本上,HashMap<String, Double> 它将具有三个键值对。

键 1:

projections - 代表每个报告中所有预测的总和。

键 2:

tickets - 代表每个报告中所有工单的总和。

键 3:

income - 这将代表每个报告的所有收入的总和。

现在,我实际上是通过创建名为 MapCollector 的自定义收集器来完成分配的。

    public class MapCollector implements Collector<Report, Map<String, Double>, Map<String, Double>>{

    @SuppressWarnings("serial")
    @Override
    public Supplier<Map<String, Double>> supplier() {

        return () ->  new HashMap<String, Double>() {{
            put("projections", 0.0);
            put("tickets", 0.0);
            put("income", 0.0);
        }};
    }

    @Override
    public BiConsumer<Map<String, Double>, Report> accumulator() {

        return (map, report) -> 
        {
            map.put("projections", map.get("projections") + report.getProjections());
            map.put("tickets", map.get("tickets") + report.getTickets());
            map.put("income", map.get("income") + report.getIncome());          
        };
    }

    @Override
    public BinaryOperator<Map<String, Double>> combiner() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Function<Map<String, Double>, Map<String, Double>> finisher() {
        // TODO Auto-generated method stub
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        // TODO Auto-generated method stub
        return Collections.unmodifiableSet(EnumSet.of(
                IDENTITY_FINISH, CONCURRENT));
    }
}

所以我得到的结果是这样的:

HashMap<String, Double> result = (HashMap<String, Double>) reports.stream().collect(new MapCollector());

所以我的问题是为什么要在不创建新的 Collector 对象的情况下以不同的方式执行此操作?也许,用groupingByreduce 以某种方式做到这一点?或者任何其他(更好)更具可读性的方式?

【问题讨论】:

  • 当然。使用具有属性和方法的实际类,而不是 Map。然后使用docs.oracle.com/javase/8/docs/api/java/util/stream/…:collect(Stats::new, Stats::addReport, Stats::addStats)。如果你想要一个不可变的归约,你也可以使用 reduce。原理类似。
  • 感谢 JB Nizet 的回复。你的意思是这样的吗? return (HashMap&lt;String, Double&gt;) reports.stream().collect(() -&gt; new HashMap&lt;String, Double&gt;() { { put("projections", 0.0); put("tickets", 0.0); put("income", 0.0); }}, (map, report) -&gt; { map.put("projections", map.get("projections") + report.getProjections()); map.put("tickets", map.get("tickets") + report.getTickets()); map.put("income", map.get("income") + report.getIncome()); }, null);该死的我不知道如何使它更具可读性。对不起先生
  • 基本上,我只是替换 MapCollector 中的 lambdas 并将它们设置在这里,“内联”。我不需要组合器。所以它是空的
  • 再次,定义一个类来保存这三个属性,而不是使用带有字符串键的映射。 collect(Stats::new, Stats::addReport, Stats::addStats)。这就是代码的样子。
  • 首先,当您的收集器不并发时不要报告CONCURRENT,即使用不支持并发更新的HashMap。其次,不要使用“花括号初始化”,它会创建一个捕获周围环境的子类,从而造成内存泄漏,例如MapCollector 实例。第三,您可以使用map.merge("projections",report.getProjections(), Double::sum) 而不是map.put("projections", map.get("projections") + report.getProjections()),同样适用于所有其他更新。组合器应该很简单,因为它看起来与累加器几乎相同。

标签: java java-stream collectors


【解决方案1】:

您实际上不需要为此目的使用自定义收集器。您只需要supplieraccumulatorcombiner

您可以将它们与流放在同一个类中,然后像下面这样调用它:

HashMap<String, Double> result = (HashMap<String, Double>) 
                                 reports.stream()
                                        .collect(
                                          this::supplier,
                                          this::acumulator,
                                          this::combiner);

方法如下所示:

private Map<String, Double> supplier() {
    return new HashMap<String, Double>() {{
        put("projections", 0.0);
        put("tickets", 0.0);
        put("income", 0.0);
    }};
}

private void accumulator(Map<String, Double> map, Report report) {
    map.merge("projections", report.getProjections(), Double::sum);
    map.merge("tickets", report.getTickets(), Double::sum);
    map.merge("income", report.getIncome(), Double::sum);
}

private void combiner(Map<String, Double> firstMap, Map<String, Double> secondMap) {
    secondMap.forEach((k, v) -> firstMap.merge(k, v, Double::sum));
}

【讨论】:

  • 嗨,我完全按照您的建议做了,但无法编译。我收到以下消息:ReportServlet 类型的供应商()类型是 HashMap,这与描述符的返回类型不兼容:R
  • 我的课程实际上是一些名为 ReportServlet 的 servlet 类...
  • 只用第二个结果覆盖第一个结果的组合器可以被认为是损坏的。写一个正确的,总结三个映射,应该不是那么难……
  • @Holger 谢谢我确实错过了这个关键点。我现在更正了,使用compute 应该没问题
  • 这可行,但与firstMap.merge(entry.getKey(), entry.getValue(), Double::sum) 相比,它过于复杂。如this comment 中所述,您也可以将merge 用于累加器。由于merge 可以正确处理缺少的键,您可以更改供应商以返回一个空映射,而不是使用双花括号初始化。
猜你喜欢
  • 2016-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-13
  • 1970-01-01
  • 2020-12-13
  • 2018-12-03
  • 2019-03-13
相关资源
最近更新 更多