Пользовательский коллектор для Collectors.groupingBy не работает должным образом - PullRequest
0 голосов
/ 12 февраля 2019

Рассмотрим простой класс 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;
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

У вас есть объяснение, почему текущий коллектор не работает, из rgettman .

Стоит проверить, какие существуют вспомогательные методы для создания пользовательских коллекторов.Например, весь этот сборщик может быть определен гораздо более кратко:

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

Не всегда возможно использовать методы, подобные этим;но краткость (и, по-видимому, хорошо проверенная) должны сделать их первым выбором, когда это возможно.

0 голосов
/ 12 февраля 2019

Во-первых, функции объединения не нужно вызывать, если вы не используете несколько потоков (параллельный поток).Комбайнер вызывается для объединения результатов операции над фрагментами вашего потока.Здесь нет параллелизма, поэтому не нужно вызывать сумматор.

Вы получаете нулевые значения из-за функции накопителя.Выражение

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, позвоните accumulate в функции accumulator и т. Д.

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