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

Gho*_*t93 8 java grouping java-stream

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

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

SELECT MAX(age)
FROM Students
GROUP BY city
Run Code Online (Sandbox Code Playgroud)

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

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());
Run Code Online (Sandbox Code Playgroud)

而另一个:

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();
Run Code Online (Sandbox Code Playgroud)

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

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

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

谢谢


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

Tag*_*eev 30

不要总是坚持下去groupingBy.有时toMap你需要的东西:

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

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


Hol*_*ger 15

作为Tagir的最佳答案的补充,使用toMap而不是groupingBy,这里是简短的解决方案,如果你想坚持groupingBy:

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

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

不过,我认为Tagir的toMap解决方案在这种情况下更可取.


groupingBy当你想获得具有最大年龄,例如学生实际基础的解决方案变得更加有趣

Collection<Student> result = students.stream().collect(
   Collectors.groupingBy(Student::getCity, Collectors.reducing(null, BinaryOperator.maxBy(
     Comparator.nullsFirst(Comparator.comparingInt(Student::getAge)))))
).values();
Run Code Online (Sandbox Code Playgroud)

实际上,即使这也可以使用toMap收集器表达:

Collection<Student> result = students.stream().collect(
    Collectors.toMap(Student::getCity, Function.identity(),
        BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge)))
).values();
Run Code Online (Sandbox Code Playgroud)

你可以用收集器表达几乎所有东西,但是groupingBy当你想要对值进行可变的减少时,它具有优势.


Rag*_*air 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);
    }
}
Run Code Online (Sandbox Code Playgroud)