问题描述
考虑简单的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;
}
}
}
1楼
首先,如果您不使用多个线程(并行流),则不需要调用合并器函数。 调用合并器以将操作结果合并到流的大块中。 这里没有并行性,因此不需要调用合并器。
由于累加器功能,您将获得零值。 表达方式
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}
2楼
您对为什么当前的收集器不起作用有一个解释。
值得检查一下存在哪些帮助程序方法来创建自定义收集器。 例如,整个收集器可以更简洁地定义为:
reducing(0.f, v -> v.v1 * v.v2, (a, b) -> a + b)
并非总是可以使用这些方法。 但是简明扼要的(并且大概是经过充分测试的)应该使它们成为可能的首选。