如何在groupingBy操作中使用自定义收集器

Tim*_*sen 4 java lambda java-stream collectors

关于使用流进行简化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)));
Run Code Online (Sandbox Code Playgroud)

上面的流代码工作得很好,但我想看看如何在使用收集器的自定义实现时执行相同的操作.我无法在Stack Overflow或网络上找到如何执行此操作的完整示例.至于为什么我们可能想要这样做,作为一个例子,也许我们想要计算某种涉及年龄的加权平均值.在这种情况下,默认行为是Collectors.averagingInt不够的.

Did*_*r L 5

只是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])
)
Run Code Online (Sandbox Code Playgroud)

虽然定义一个可能更具可读性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;
    }
}
Run Code Online (Sandbox Code Playgroud)

并将其用作:

Collector.of(PersonAverager::new,
        PersonAverager::accept,
        PersonAverager::combine,
        PersonAverager::average)
Run Code Online (Sandbox Code Playgroud)

  • @FedericoPeraltaSchaffner说实话,我虽然这样做,但你已经有了`Collectors.averingInt/Long/Double()`,它确实做到了(对于`double`来说好多了).我认为在这里保持简单更为重要,因为当你想要做其他事情而不是平均时,它更容易概括. (2认同)