![]() |
傻傻的针织衫 · 分布式事务原理及解决方案 - 夏尔_717 ...· 6 月前 · |
![]() |
面冷心慈的八宝粥 · 什么是约束?有哪些基本概念_云原生数据库 ...· 8 月前 · |
![]() |
求醉的酸菜鱼 · iOS小技能:【intercept the ...· 8 月前 · |
![]() |
潇洒的大海 · 如何选购投影幕布? - 知乎· 1 年前 · |
![]() |
道上混的墨镜 · 嵌入式Qt-做一个秒表-腾讯云开发者社区-腾讯云· 1 年前 · |
假设我有以下
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语句。
谢谢
编辑: 下面所有的答案都很有趣,但不幸的是这不是我想要的,因为我试图得到的只是值。我不需要键,只需要值。
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);
}
第二种方法在
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());
不要总是坚持使用
groupingBy
。有时
toMap
是你需要的东西:
Collection<Integer> result = students.stream()
.collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
.values();
在这里,您只需创建一个
Map
,其中键是城市,值是年龄。当几个学生有相同的城市时,使用合并函数,在这里只选择最大年龄。它更快更干净。
作为使用
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
有自己的优势。