[英]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.