当前位置: 代码迷 >> java >> Custom Collector for Collectors.groupingBy无法正常工作
  详细解决方案

Custom Collector for Collectors.groupingBy无法正常工作

热度:128   发布时间:2023-07-27 09:13:01.0

考虑简单的Foo类:

public class Foo {

    public Float v1;
    public Float v2;
    public String name;

    public Foo(String name, Float v1, Float v2) {
        this.name = name;
        this.v1 = v1;
        this.v2 = v2;
    }

    public String getName() {
        return name;
    }
}

现在,我有一个Foo的集合,我想按Foo::getName将它们分组。 我编写了一个自定义收集器来执行此操作,但是它似乎没有按预期工作。 更确切地说, combiner()永远不会被调用。 为什么?

public class Main {

    public static void main(String[] args) {

        List<Foo> foos = new ArrayList<>();
        foos.add(new Foo("blue", 2f, 2f));
        foos.add(new Foo("blue", 2f, 3f));
        foos.add(new Foo("green", 3f, 4f));

        Map<String, Float> fooGroups = foos.stream().collect(Collectors.groupingBy(Foo::getName, new FooCollector()));
        System.out.println(fooGroups);
    }

    private static class FooCollector implements Collector<Foo, Float, Float> {

        @Override
        public Supplier<Float> supplier() {
            return () -> new Float(0);
        }

        @Override
        public BiConsumer<Float, Foo> accumulator() {
            return (v, foo) -> v += foo.v1 * foo.v2;
        }

        @Override
        public BinaryOperator<Float> combiner() {
            return (v1, v2) -> v1 + v2;
        }

        @Override
        public Function<Float, Float> finisher() {
            return Function.identity();
        }

        @Override
        public Set<Characteristics> characteristics() {
            Set<Characteristics> characteristics = new TreeSet<>();
            return characteristics;
        }
    }
}

首先,如果您不使用多个线程(并行流),则不需要调用合并器函数。 调用合并器以将操作结果合并到流的大块中。 这里没有并行性,因此不需要调用合并器。

由于累加器功能,您将获得零值。 表达方式

v += foo.v1 * foo.v2;

v 替换为新的Float对象。 原始累加器对象未修改; 它仍然是0f 此外, Float像其他数字包装类型(和String )一样是不可变的,不能更改。

您需要某种可变的其他累加器对象。

class FloatAcc {
    private Float total;
    public FloatAcc(Float initial) {
        total = initial;
    }
    public void accumulate(Float item) {
        total += item;
    }
    public Float get() {
        return total;
    }
}

然后,您可以修改自定义Collector以使用FloatAcc 提供一个新的FloatAcc ,在accumulator函数中调用accumulate等。

class FooCollector implements Collector<Foo, FloatAcc, Float> {
    @Override
    public Supplier<FloatAcc> supplier() {
        return () -> new FloatAcc(0f);
    }
    @Override
    public BiConsumer<FloatAcc, Foo> accumulator() {
        return (v, foo) -> v.accumulate(foo.v1 * foo.v2);
    }
    @Override
    public BinaryOperator<FloatAcc> combiner() {
        return (v1, v2) -> {
            v1.accumulate(v2.get());
            return v1;
        };
    }
    @Override
    public Function<FloatAcc, Float> finisher() {
        return FloatAcc::get;
    }
    @Override
    public Set<Characteristics> characteristics() {
        Set<Characteristics> characteristics = new TreeSet<>();
        return characteristics;
    }
}

通过这些更改,我得到了您的期望:

{green=12.0, blue=10.0}

您对为什么当前的收集器不起作用有一个解释。

值得检查一下存在哪些帮助程序方法来创建自定义收集器。 例如,整个收集器可以更简洁地定义为:

reducing(0.f, v -> v.v1 * v.v2, (a, b) -> a + b)

并非总是可以使用这些方法。 但是简明扼要的(并且大概是经过充分测试的)应该使它们成为可能的首选。

  相关解决方案