繁体   English   中英

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

[英]Java 8 Stream API - Selecting only values after Collectors.groupingBy(..)

假设我有以下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语句进行over partition by ...

谢谢


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

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

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

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

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

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

需要注意的是这三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();

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

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

您可以使用两个收集器表达几乎所有内容,但是当您想要对值执行可变减少时, groupingBy具有优势。

第二种方法在Optional上调用get() ; 这通常是一个坏主意,因为您不知道可选项是否为空(使用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());
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);
    }
}
        List<BeanClass> list1 = new ArrayList<BeanClass>();
        DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        list1.add(new BeanClass(123,abc,99.0,formatter.parse("2018-02-01")));
        list1.add(new BeanClass(456,xyz,99.0,formatter.parse("2014-01-01")));
        list1.add(new BeanClass(789,pqr,95.0,formatter.parse("2014-01-01")));
        list1.add(new BeanClass(1011,def,99.0,formatter.parse("2014-01-01")));
        Map<Object, Optional<Double>> byDate = list1.stream()
       .collect(Collectors.groupingBy(p -> formatter.format(p.getCurrentDate()),
        Collectors.mapping(BeanClass::getAge, Collectors.maxBy(Double::compare))));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM