使用 Java 流从列表中的列表平均特定值

Average specific values from a list within a list using Java stream

无法弄清楚如何读出特定建筑物列表中动物的平均体重。

我写了另一种方法来获取每种动物的名称,所以我知道我的坚持是有效的。

动物园:

public class Zoo {
    private List<Animal> animals;
    private List<Building> buildings;

    public Zoo() {
        this.animals = new PersistencyController().giveAnimals();
        this.gebouwen = new PersistencyController().giveBuildings();
    }

 public List<Animal> giveAnimalsByKind(String kindName) {
        return animals.stream().filter(animal -> animal.getKind().getName() == kindName).sorted(Comparator.comparing(Animal::getWeight)).collect(Collectors.toList());
    }

    public double giveAvgWeightForAnimalsInBuilding(String buildingName) {
        return animals.stream().collect(Collectors.averagingDouble(Animal::getWeight));
    }
}

动物:

public class Animal implements Serializable {

    private int nr;
    private String name;
    private double weight;
    private Kind kind;

    public Animal(int nr, String name, double weight, Kind kind) {
        this.nr = nr;
        this.name = name;
        this.weight= weight;
        this.kind= kind;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }
}

建筑物:

public class Building{

    private String naam;
    private int capaciteit;
    private final List<Animal> animals = new ArrayList<>();

    public Building(String naam, int capaciteit) {
        this.naam = naam;
        this.capaciteit = capaciteit;
    }
}

我想获得每栋楼动物的平均体重,所以我的想法是传递建筑物的名称。每个 Building 对象都存储一个动物列表,我需要 building 对象,因为它存储了建筑物的名称,但我不知道如何使用流访问它。我知道在我发布的代码中,我实际上还没有使用 buildingName,但那是因为我什至无法正常获得动物的平均体重。 weight 在 Animal 中存储为 double 并且我的坚持正在发挥作用,因为我已经以其他方式对此进行了测试。非常感谢任何对流有经验的人解释如何通过构建进行过滤并得到这个平均权重。

如果您可以自由使用 Java9,我建议您使用 flatMapping 收集器来完成这件事。这是它的样子。

Map<String, Double> avgWeightByBuildingName = buildings.stream()
    .collect(Collectors.groupingBy(Building::getName,
        Collectors.flatMapping(b -> b.getAnimals().stream(), 
            Collectors.averagingDouble(Animal::getWeight))));

然而,这里的 java8 解决方案与前者相比看起来有点冗长。

Map<String, Double> avgWeightByBuildingName = buildings.stream()
    .flatMap(b -> b.getAnimals().stream()
        .map(a -> new AbstractMap.SimpleEntry<>(b.getName(), a.getWeight())))
    .collect(Collectors.groupingBy(Map.Entry::getKey, 
        Collectors.averagingDouble(Map.Entry::getValue)));

或者考虑按照此 编写您自己的 flatMapping 收集器。这是我在查看上述答案和 JDK 9 源代码后想出的一个这样的实现。

static <T, U, A, R> Collector<T, ?, R> flatMapping(Function<? super T,
            ? extends Stream<? extends U>> mapper, Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(), (r, t) -> {
        try (Stream<? extends U> result = mapper.apply(t)) {
            result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
        }
    }, downstream.combiner(), downstream.finisher(), 
            downstream.characteristics().toArray(new Characteristics[0]));
}

此外,对于较新的 Java 版本,它有一个直接的迁移策略,如以下评论所述。