Java 收集器的自定义收集器。groupingBy不';I don’我没有按预期工作

Java 收集器的自定义收集器。groupingBy不';I don’我没有按预期工作,java,java-8,java-stream,collectors,Java,Java 8,Java Stream,Collectors,考虑一下简单的类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 getNa

考虑一下简单的类
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
s,我想按
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;
        }
    }
}
公共类主{
公共静态void main(字符串[]args){
List foos=new ArrayList();
添加(新的Foo(“蓝色”,2f,2f));
添加(新的Foo(“蓝色”,2f,3f));
添加(新的Foo(“绿色”,3f,4f));
Map fooGroups=foos.stream().collect(Collectors.groupingBy(Foo::getName,new FooCollector());
System.out.println(组);
}
私有静态类FooCollector实现收集器{
@凌驾
公共供应商(){
返回()->新浮点数(0);
}
@凌驾
公共双消费者累加器(){
返回(v,foo)->v+=foo.v1*foo.v2;
}
@凌驾
公共二进制运算符组合器(){
返回(v1,v2)->v1+v2;
}
@凌驾
公共函数完成器(){
返回函数.identity();
}
@凌驾
公共集特征(){
设置特征=新树集();
收益特性;
}
}
}

首先,如果不使用多线程(并行流),则无需调用合并器函数。将调用组合器来组合流块上的操作结果。这里没有并行性,因此不需要调用组合器

由于累加器函数,您得到的值为零。表情

v += foo.v1 * foo.v2;
将用新的
Float
对象替换
v
。未修改原始累加器对象;它仍然是
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;
    }
}
然后,您可以修改自定义的
收集器
以使用
FloatAcc
。提供新的
FloatAcc
,在
累加器
功能中调用
累加
,等等

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

您有一个解释,说明为什么电流采集器不工作

值得检查一下,看看有哪些辅助方法可以创建自定义收集器。例如,整个收集器可以更简洁地定义为:

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

使用这样的方法并不总是可能的;但是简洁性(可能还有良好的测试性)应该尽可能让它们成为首选。

因为输入流不是平行的。仅供参考,可能是一种更简洁的声明收集器的方法。你得到的是两个0而不是10和12吗?@argetman,是的。流小不是也很重要?因此,即使它是一个并行流,3个元素也会被当作顺序流处理,因为并行处理3件事情的开销是不值得的。@Andy Turner我不知道并行流如何决定并行的内部细节,但这听起来很合理。是的,事实上我会同意的!更整洁、更简单@当你请求并行时,你得到并行,不管它是否值得。流API不能预先决定每个元素的处理成本是否能够证明并行处理的合理性。因此,即使是具有两个元素的
ArrayList
,也可以使用组合器并行处理。唯一的例外是,如果第一个线程真的以如此快的速度处理第一个元素,那么它可以在提取第二个元素之前偷走第二个元素的工作负载。然后,它是否使用组合器取决于具体的实现。@rgetman没有理由使用
FloatAcc
而不是
Float
。这破坏了使用
collect
而不是
reduce
的全部好处。因此,它应该使用
float
,或者用单个元素
float[]
数组替换整个类,就像内置收集器在内部所做的那样。说到内置收集器,
collectionandthen(summingDouble(foo->foo.v1*foo.v2),d->(float)d)
就可以了。当组中有很多元素时,使用
collectionandthen(summingDouble(foo->foo.v1*foo.v2),d->(float)d可以避免装箱开销
取而代之。@Holger我甚至会避免使用
收集然后
。只要
summingDouble(foo->foo.v1*foo.v2)
就可以了,如果OP真的想要
float
而不是
double
,一个简单的cast就可以了。(但我承认这是一个品味和编码风格的问题,即完全主观)。@FedericoPeraltaSchaffner OP使用此收集器作为
groupingBy
的下游收集器,以获得
Map
,因此在这种特定情况下,您需要
收集,然后
(除非切换到
Map
是一个选项)霍尔格说得对。我忽略了OP将其用作下游收集器。
reducing(0.f, v -> v.v1 * v.v2, (a, b) -> a + b)