首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Java8Stream API -仅选择Collectors.groupingBy(..)之后的值

Java8Stream API -仅选择Collectors.groupingBy(..)之后的值
EN

Stack Overflow用户
提问于 2016-03-01 06:04:26
回答 5查看 14.5K关注 0票数 9

假设我有以下Student对象集合,它由Name(String)、Age(int)和City(String)组成。

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

代码语言:javascript
运行
复制
SELECT MAX(age)
FROM Students
GROUP BY city

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

代码语言:javascript
运行
复制
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());

另一个是:

代码语言:javascript
运行
复制
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语句。

谢谢

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

EN

回答 5

Stack Overflow用户

发布于 2016-03-01 13:19:19

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

代码语言:javascript
运行
复制
Collection<Integer> result = students.stream()
    .collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
    .values();

在这里,您只需创建一个Map,其中键是城市,值是年龄。当几个学生有相同的城市时,使用合并函数,在这里只选择最大年龄。它更快更干净。

票数 31
EN

Stack Overflow用户

发布于 2016-03-01 18:45:01

作为使用toMap而不是groupingByTagir’s great answer的补充,这里是简短的解决方案,如果您想坚持使用groupingBy

代码语言:javascript
运行
复制
Collection<Integer> result = students.stream()
    .collect(Collectors.groupingBy(Student::getCity,
                 Collectors.reducing(-1, Student::getAge, Integer::max)))
    .values();

请注意,这三个参数的reducing收集器已经执行了映射操作,因此我们不需要将其与mapping收集器一起嵌套,此外,提供一个标识值可以避免处理Optional。由于年龄始终是正的,提供-1就足够了,而且由于组始终至少有一个元素,因此身份值永远不会显示出来。

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

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

代码语言:javascript
运行
复制
Collection<Student> result = students.stream().collect(
   Collectors.groupingBy(Student::getCity, Collectors.reducing(null, BinaryOperator.maxBy(
     Comparator.nullsFirst(Comparator.comparingInt(Student::getAge)))))
).values();

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

代码语言:javascript
运行
复制
Collection<Student> result = students.stream().collect(
    Collectors.toMap(Student::getCity, Function.identity(),
        BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge)))
).values();

您可以用这两个收集器表达几乎所有内容,但是当您想对值执行mutable reduction时,groupingBy有自己的优势。

票数 15
EN

Stack Overflow用户

发布于 2016-03-01 06:29:02

第二种方法在Optional上调用get();这通常不是一个好主意,因为您不知道optional是否为空(改为使用orElse()orElseGet()orElseThrow()方法)。虽然您可能会认为在这种情况下总是有一个值,因为您是从学生列表本身生成值的,但这一点需要牢记。

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

代码语言:javascript
运行
复制
final Collection<Integer> variation2 =
     students.stream()
             .collect(collectingAndThen(groupingBy(Student::getCity,
                                                   collectingAndThen(
                                                      mapping(Student::getAge, maxBy(naturalOrder())),
                                                      Optional::get)), 
                                        Map::values));

尽管它确实开始变得难以阅读,但我可能会使用变体1:

代码语言:javascript
运行
复制
final List<Integer> variation1 =
        students.stream()
            .collect(groupingBy(Student::getCity,
                                mapping(Student::getAge, maxBy(naturalOrder()))))
            .values()
            .stream()
            .map(Optional::get)
            .collect(toList());
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35710619

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档