Collectors.reducing 方法在用作 Collectors.partitionBy 的下游时更新相同的身份

问题描述

我有一个类似于下面 MyObject 的类。

public class MyObject {
    private String key; // not unique. multiple objects can have the same key.
    private boolean isPermanent;
    private double value1;
    private double value2;
    
    public MyObject merge(MyObject m){
        value1 += m.getValue1();
        value2 += m.getValue2();
        return this;
    }

    // getters,setters and constructors...
}

以下是样本数据:

List<MyObject> objs = new ArrayList<>();

objs.add(new MyObject("1",false,100,200));
objs.add(new MyObject("2",300,100));
objs.add(new MyObject("1",200,300));

objs.add(new MyObject("3",true,200));
objs.add(new MyObject("1",500,100));

我想根据 isPermanentkey 组合这些对象并执行以下操作:

(请注意,我在下面的代码添加import static java.util.stream.Collectors.* 以导入 groupingBypartitioningByreducing

objs.stream()
    .collect(partitioningBy(MyObject::isPermanent,groupingBy(MyObject::getKey,reducing(new MyObject(),MyObject::merge))));

返回的地图类型为 Map<Boolean,Map<String,MyObject>>。 我希望返回的地图如下(忽略 value1value2 以外的字段)

{false : { 1 : { 300,500 } },{ 2 : { 300,100 } },true : { 1 : { 600,200 } },{ 3 : { 100,200 } } }

但我得到的结果是:

{false : { 1 : { 1300,1000 } },{ 2 : { 1300,true : { 1 : { 1300,{ 3 : { 1300,1000 } } }

由于我传递了一个对象作为身份,我相信每个组都会更新同一个对象。由于无法将 lambda 传递给 reduction 方法,有没有办法解决这个问题?

解决方法

您可以使用返回新实例的 static 合并函数:

public static MyObject merge(MyObject one,MyObject two) {
    MyObject merged = new MyObject ();
    merged.setValue1(one.getValue1()+two.getValue1());
    merged.setValue2(one.getValue2()+two.getValue2());
    return merged;
}

您必须删除现有的非静态 merge 方法,以便编译器选择 static 方法代替方法引用。

这样,MyObject::merge 每次都会产生一个新的 MyObject 实例。

如果您不想删除现有方法,如果将方法引用替换为以下 lambda 表达式,您仍然可以添加 static 方法:

(o1,o2)->MyObject.merge(o1,o2)

无需添加 static 方法,您可以使用以下 lambda 表达式替换 MyObject::merge 方法引用:

(o1,o2)-> new MyObject().merge(o1).merge(o2)
,

基于上面Holger's中的answer注释,使用reducing代替Collector.of会更好。

对于有问题的给定 List<MyObject> objs,以下使用 reducing 的代码将生成 7 类型为 MyObject 的新对象。 (生成一个新对象作为标识,每次归约生成一个)。

groupingBy(MyObject::getKey,reducing((o1,o2)-> new MyObject().merge(o1).merge(o2)));

但是使用 mutable reductionCollectors,创建的对象数量将减少到 4,这是需要为有问题的给定输入创建的最佳对象数量. (分别用于 truefalse 分区的 2 个缩减对象)。

Collector<MyObject,MyObject,MyObject> objCollector = Collector.of(MyObject::new,MyObject::merge,MyObject::merge);

objs.stream()
    .collect(partitioningBy(MyObject::isPermanent,groupingBy(MyObject::getKey,objCollector)));