Как использовать пользовательский Collector в операции groupingBy - PullRequest
0 голосов
/ 27 сентября 2018

Оракул Oracle следит за сокращением с потоками, дает пример того, как преобразовать коллекцию людей в карту, содержащую средний возраст по полу.Он использует следующий класс и код Person:

public class Person {
    private int age;

    public enum Sex {
        MALE,
        FEMALE
    }

    private Sex sex;

    public Person (int age, Sex sex) {
        this.age = age;
        this.sex = sex;
    }

    public int getAge() { return this.age; }

    public Sex getSex() { return this.sex; }
}

Map<Person.Sex, Double> averageAgeByGender = roster
    .stream()
    .collect(
        Collectors.groupingBy(
            Person::getSex,                      
            Collectors.averagingInt(Person::getAge)));

Приведенный выше код потока прекрасно работает, но я хотел посмотреть, как выполнить ту же операцию при использовании реализации custom коллекционер.Я не смог найти полный пример того, как это сделать, ни в переполнении стека, ни в сети.Что касается того, почему мы могли бы захотеть сделать это, например, возможно, мы бы хотели вычислить какое-то средневзвешенное значение с учетом возраста.В этом случае поведения по умолчанию Collectors.averagingInt будет недостаточно.

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Просто используйте Collector.of(Supplier, BiConsumer, BinaryOperator, [Function,] Characteristics...) для тех случаев:

Collector.of(() -> new double[2],
        (a, t) -> { a[0] += t.getAge(); a[1]++; },
        (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; },
        a -> (a[1] == 0) ? 0.0 : a[0] / a[1])
)

Хотя это может быть более читабельным для определения PersonAverager:

class PersonAverager {
    double sum = 0;
    int count = 0;

    void accept(Person p) {
        sum += p.getAge();
        count++;
    }

    PersonAverager combine(PersonAverager other) {
        sum += other.sum;
        count += other.count;
        return this;
    }

    double average() {
        return count == 0 ? 0 : sum / count;
    }
}

ииспользуйте это как:

Collector.of(PersonAverager::new,
        PersonAverager::accept,
        PersonAverager::combine,
        PersonAverager::average)
0 голосов
/ 27 сентября 2018

Этот ответ, который был проверен, основан на нескольких источниках.Исходный код Collectors#averagingInt был полезен при выяснении синтаксиса лямбда-выражения, используемого ниже.Используется поставщик Double[] массив второго размера.Первый индекс используется для хранения совокупного возраста человека, а второй - для счетчиков.

public class PersonCollector<T extends Person> implements Collector<T, double[], Double> {
    private ToIntFunction<Person> mapper;

    public PersonCollector(ToIntFunction<Person> mapper) {
        this.mapper = mapper;
    }

    @Override
    public Supplier<double[]> supplier() {
        return () -> new double[2];
    }

    @Override
    public BiConsumer<double[], T> accumulator() {
        return (a, t) -> { a[0] += mapper.applyAsInt(t); a[1]++; };
    }

    @Override
    public BinaryOperator<double[]> combiner() {
        return (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; };
    }

    @Override
    public Function<double[], Double> finisher() {
        return a -> (a[1] == 0) ? 0.0 : a[0] / a[1];
    }

    @Override
    public Set<Characteristics> characteristics() {
        // do NOT return IDENTITY_FINISH here, which would bypass
        // the custom finisher() above
        return Collections.emptySet();
    }
}

List<Person> list = new ArrayList<>();
list.add(new Person(34, Person.Sex.MALE));
list.add(new Person(23, Person.Sex.MALE));
list.add(new Person(68, Person.Sex.MALE));
list.add(new Person(14, Person.Sex.FEMALE));
list.add(new Person(58, Person.Sex.FEMALE));
list.add(new Person(27, Person.Sex.FEMALE));

final Collector<Person, double[], Double> pc = new PersonCollector<>(Person::getAge);

Map<Person.Sex, Double> averageAgeBySex = list
  .stream()
  .collect(Collectors.groupingBy(Person::getSex, pc));

System.out.println("Male average: " + averageAgeBySex.get(Person.Sex.MALE));
System.out.println("Female average: " + averageAgeBySex.get(Person.Sex.FEMALE));

Это выводит:

Male average: 41.666666666666664
Female average: 33.0

Обратите внимание, что мы передаем ссылку на метод Person::getAge пользовательскому коллектору, который отображает каждый Person в коллекции на целочисленное значение возраста.Кроме того, мы не возвращаем Characteristics.IDENTITY_FINISH из метода characateristics().Это будет означать, что наш пользовательский finisher() будет обойден.

...