Java 8 Stream API - 在Collectors.groupingBy(..)之后仅选择值

时间:2016-02-29 22:04:26

标签: java grouping java-stream

假设我有以下Student个对象集合,包括Name(String),Age(int)和City(String)。

我正在尝试使用Java的Stream API来实现以下类似sql的行为:

SELECT MAX(age)
FROM Students
GROUP BY city

现在,我发现了两种不同的方法:

final List<Integer> variation1 =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity, Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge())))
                    .values()
                    .stream()
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .map(Student::getAge)
                    .collect(Collectors.toList());

另一个:

final Collection<Integer> variation2 =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity,
                            Collectors.collectingAndThen(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()),
                                    optional -> optional.get().getAge())))
                    .values();

在两种方式中,都必须.values() ...并过滤从收集器返回的空组。

有没有其他方法可以实现这种必要的行为?

这些方法让我想起了over partition by sql语句......

由于

编辑:下面的所有答案都非常有趣,但不幸的是,这不是我想要的,因为我试图获得的只是价值观。我不需要键,只需要值。

4 个答案:

答案 0 :(得分:29)

不要总是坚持使用groupingBy。有时toMap就是你需要的东西:

Collection<Integer> result = students.stream()
    .collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
    .values();

在这里,您只需创建一个Map,其中键是城市,值是年龄。如果几个学生拥有相同的城市,则使用合并功能,这里只选择最大年龄。它更快更清洁。

答案 1 :(得分:12)

作为使用toMap代替groupingBy的{​​{3}}的补充,这里是简短的解决方案,如果您想坚持groupingBy

Collection<Integer> result = students.stream()
    .collect(Collectors.groupingBy(Student::getCity,
                 Collectors.reducing(-1, Student::getAge, Integer::max)))
    .values();

请注意,这三个arg reducing收集器已经执行了映射操作,因此我们不需要将它与mapping收集器嵌套,进一步提供标识值以避免处理{{1 }}。由于年龄总是正数,因此提供Optional就足够了,因为一个组总是至少有一个元素,所以身份值永远不会显示为结果。

尽管如此,我认为在这种情况下,Tagir基于-1的解决方案更可取。

当您希望让实际学生达到最大年龄时,基于toMap的解决方案会变得更有趣,例如

groupingBy
实际上,即使这也可以使用Collection<Student> result = students.stream().collect( Collectors.groupingBy(Student::getCity, Collectors.reducing(null, BinaryOperator.maxBy( Comparator.nullsFirst(Comparator.comparingInt(Student::getAge))))) ).values(); 收集器来表达:

toMap

您可以使用两个收藏家表达几乎所有内容,但如果您想对这些值执行Tagir’s great answer,则Collection<Student> result = students.stream().collect( Collectors.toMap(Student::getCity, Function.identity(), BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge))) ).values(); 会有优势。

答案 2 :(得分:1)

第二种方法在get()上调用Optional;这通常是一个坏主意,因为您不知道可选项是否为空(请改用orElse()orElseGet()orElseThrow()方法。虽然您可能会争辩说,在这种情况下,由于您从学生列表本身生成值,因此始终存在一个值,这是需要牢记的。

基于此,您可以将变体2转换为:

final Collection<Integer> variation2 =
     students.stream()
             .collect(collectingAndThen(groupingBy(Student::getCity,
                                                   collectingAndThen(
                                                      mapping(Student::getAge, maxBy(naturalOrder())),
                                                      Optional::get)), 
                                        Map::values));

虽然它真的开始难以阅读,但我可能会使用变体1:

final List<Integer> variation1 =
        students.stream()
            .collect(groupingBy(Student::getCity,
                                mapping(Student::getAge, maxBy(naturalOrder()))))
            .values()
            .stream()
            .map(Optional::get)
            .collect(toList());

答案 3 :(得分:0)

Here is my implementation 

    public class MaxByTest {

        static class Student {

        private int age;
        private int city;        

        public Student(int age, int city) {
            this.age = age;
            this.city = city;
        }

        public int getCity() {
            return city;
        }

        public int getAge() {
            return age;
        }

        @Override
        public String toString() {
            return " City : " + city + " Age : " + age;
        }



    }

    static List<Student> students = Arrays.asList(new Student[]{
        new Student(10, 1),
        new Student(9, 2),        
        new Student(8, 1),        
        new Student(6, 1),
        new Student(4, 1),
        new Student(8, 2),
        new Student(9, 2),
        new Student(7, 2),        
    });

    public static void main(String[] args) {
        final Comparator<Student> comparator = (p1, p2) -> Integer.compare( p1.getAge(), p2.getAge());
        final List<Student> studets =
            students.stream()
                    .collect(Collectors.groupingBy(Student::getCity, 
                            Collectors.maxBy(comparator))).values().stream().map(Optional::get).collect(Collectors.toList());
        System.out.println(studets);
    }
}