Java 8 Collector.groupingBy 下游分类器值

Java 8 Collector.groupingBy classifier value in downstream

在以下示例中,将分类器值提供给收集器的供应商函数的正确方法是什么:

import static java.math.BigDecimal.*;
import static java.util.stream.Collector.*;
import static java.util.stream.Collectors.*;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Test {

    public static class Item {
        String key;
        BigDecimal a;
        BigDecimal b;
        public Item(String key, BigDecimal a, BigDecimal b) {
            this.key = key;
            this.a = a;
            this.b = b;
        }
        public String getKey() {
            return key;
        }
        public BigDecimal getA() {
            return a;
        }
        public BigDecimal getB() {
            return b;
        }


    }
    public static class ItemSum {
        public ItemSum() {
        }

        public ItemSum(String key) {
            this.key = key;
        }
        String key;
        BigDecimal sumA = ZERO;
        BigDecimal sumB = ZERO;
        public void add(BigDecimal a, BigDecimal b) {
            sumA = sumA.add(a);
            sumB = sumB.add(b);
        }

        public ItemSum merge(ItemSum is) {
            sumA = sumA.add(is.getSumA());
            sumB = sumB.add(is.getSumB());
            return this;
        }
        public BigDecimal getSumA() {
            return sumA;
        }
        public BigDecimal getSumB() {
            return sumB;
        }


    }

    public static void main(String[] args) {

        Map<String, ItemSum> map = list().stream().collect(
                groupingBy(Item::getKey, 
                        of(
                                ItemSum::new, 
                                (s,i) -> {s.add(i.getA(), i.getB());},
                                (i,j) -> {return i.merge(j);}
                                )
                        )
                );
        map.forEach((k,v) -> {System.out.println(String.format("%s: A: %s: B: %s", k, v.getSumA(), v.getSumB()));});
    }

    public static List<Item> list() {
        List<Item> list = new ArrayList<>(3);
        list.add(new Item("a", ONE, ONE));
        list.add(new Item("a", ONE, ONE));
        list.add(new Item("b", ONE, ONE));
        return list;
    }

}

问题是在构建期间将 key 值传递给 ItemSum。之后我有一些解决方法来填充关键字段,但我想知道是否有办法在供应商中做到这一点并消除 ItemSum.

的默认构造函数

这是 toMap(keyMapper, valueMapper, mergeFunction) 收集器的工作,而不是 groupingBy 收集器的工作。

让我们添加一个构造函数

public ItemSum(Item item) {
    this.key = item.getKey();
    this.sumA = item.getA();
    this.sumB = item.getB();
}

到classItemSum。此构造函数基于 Item 初始化一个 ItemSum。然后您可以删除默认构造函数(不需要)。然后你可以简单地拥有以下内容:

Map<String, ItemSum> map = 
    list().stream()
          .collect(Collectors.toMap(Item::getKey, ItemSum::new, ItemSum::merge));

它所做的是将每个 Item 元素收集到一个由每个 Item 的键定义的映射 class 中。对于这个值,我们用一个 ItemSum 初始化它。当遇到同一个键的多个值时,我们将它们合并在一起。

总之,不能随心所欲

如果你让你的 ItemSum class 在你的 add 方法(收集器的累加器方法)中接收一个 Item 实例,你可以轻松实现你想要的想要:

public class ItemSum {

    String key;

    BigDecimal sumA = ZERO;

    BigDecimal sumB = ZERO;

    public void add(Item item) {
        key = item.getKey();
        sumA = sumA.add(item.getA());
        sumB = sumB.add(item.getB());
    }

    public ItemSum merge(ItemSum is) {
        sumA = sumA.add(is.getSumA());
        sumB = sumB.add(is.getSumB());
        return this;
    }

    public BigDecimal getSumA() {
        return sumA;
    }

    public BigDecimal getSumB() {
        return sumB;
    }
}

那么,这样使用:

Map<String, ItemSum> map = list().stream()
    .collect(Collectors.groupingBy(
        Item::getKey, 
        Collector.of(
            ItemSum::new, 
            ItemSum::add,
            ItemSum::merge)));