Collectors.reducing 方法在用作 Collectors.partitionBy 的下游时正在更新相同的标识

Collectors.reducing method is updating the same identity when used as downstream for Collectors.partitionBy

我有一个 class 类似于下面的 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", false, 300, 100));
objs.add(new MyObject("1", false, 200, 300));

objs.add(new MyObject("3", true, 100, 200));
objs.add(new MyObject("1", true, 500, 100));
objs.add(new MyObject("1", true, 100, 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, 1000 } }, true : { 1 : { 1300, 1000 } } , { 3 : { 1300, 1000 } } }

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

您可以使用 static 合并函数 returns 新实例:

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实例。

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

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

在不添加static方法的情况下,可以将MyObject::merge方法引用替换为以下lambda表达式:

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

基于Holger's comment in the above ,不用reducing,最好用Collector.of.

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

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)));