假设我有以下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语句。
谢谢
编辑:下面所有的答案都很有趣,但不幸的是这不是我想要的,因为我试图得到的只是值。我不需要键,只需要值。
发布于 2016-03-01 13:19:19
不要总是坚持使用groupingBy
。有时toMap
是你需要的东西:
Collection<Integer> result = students.stream()
.collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
.values();
在这里,您只需创建一个Map
,其中键是城市,值是年龄。当几个学生有相同的城市时,使用合并函数,在这里只选择最大年龄。它更快更干净。
发布于 2016-03-01 18:45:01
作为使用toMap
而不是groupingBy
的Tagir’s great answer的补充,这里是简短的解决方案,如果您想坚持使用groupingBy
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
的解决方案变得更加有趣,例如
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();
您可以用这两个收集器表达几乎所有内容,但是当您想对值执行mutable reduction时,groupingBy
有自己的优势。
发布于 2016-03-01 06:29:02
第二种方法在Optional
上调用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());
https://stackoverflow.com/questions/35710619
复制相似问题