Java 8 Stream API - 在 Collectors.groupingBy(..) 之后只选择值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35710619/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8 Stream API - Selecting only values after Collectors.groupingBy(..)
提问by Ghost93
Say I have the following collection of Student
objects which consist of Name(String), Age(int) and City(String).
假设我有以下Student
对象集合,它们由 Name(String)、Age(int) 和 City(String) 组成。
I am trying to use Java's Stream API to achieve the following sql-like behavior:
我正在尝试使用 Java 的 Stream API 来实现以下类似 sql 的行为:
SELECT MAX(age)
FROM Students
GROUP BY city
Now, I found two different ways to do so:
现在,我找到了两种不同的方法:
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());
And the other one:
而另一个:
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();
In both ways, one has to .values() ...
and filter the empty groups returned from the collector.
在这两种方式中,都必须.values() ...
过滤收集器返回的空组。
Is there any other way to achieve this required behavior?
有没有其他方法可以实现这种所需的行为?
These methods remind me of over partition by
sql statements...
这些方法让我想起了over partition by
sql语句...
Thanks
谢谢
Edit:All the answers below were really interesting, but unfortunately this is not what I was looking for, since what I try to get is just the values. I don't need the keys, just the values.
编辑:下面的所有答案都非常有趣,但不幸的是,这不是我想要的,因为我试图得到的只是值。我不需要键,只需要值。
回答by Tagir Valeev
Do not always stick with groupingBy
. Sometimes toMap
is the thing you need:
不要总是坚持groupingBy
。有时toMap
是你需要的东西:
Collection<Integer> result = students.stream()
.collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
.values();
Here you just create a Map
where keys are cities and values are ages. In case when several students have the same city, merge function is used which just selects maximal age here. It's faster and cleaner.
在这里,您只需创建一个Map
键是城市,值是年龄的对象。如果几个学生有同一个城市,则使用合并功能,这里只选择最大年龄。它更快更干净。
回答by Holger
As addition to Tagir's great answerusing toMap
instead of groupingBy
, here the short solution, if you want to stick to groupingBy
:
至于除了Tagir最伟大的答案使用toMap
的,而不是groupingBy
,这里的短期解决办法,如果你想坚持到groupingBy
:
Collection<Integer> result = students.stream()
.collect(Collectors.groupingBy(Student::getCity,
Collectors.reducing(-1, Student::getAge, Integer::max)))
.values();
Note that this three arg reducing
collector already performs a mapping operation, so we don't need to nest it with a mapping
collector, further, providing an identity value avoids dealing with Optional
. Since ages are always positive, providing -1
is sufficient and since a group will always have at least one element, the identity value will never show up as a result.
注意这三个 argreducing
收集器已经执行了映射操作,所以我们不需要用mapping
收集器嵌套它,进一步,提供一个标识值避免处理Optional
. 由于年龄总是正数,提供-1
就足够了,而且由于一个群体总是至少有一个元素,因此身份值永远不会出现。
Still, I think Tagir's toMap
based solution is preferable in this scenario.
不过,我认为toMap
在这种情况下,基于Tagir 的解决方案更可取。
The groupingBy
based solution becomes more interesting when you want to get the actual students having the maximum age, e.g
groupingBy
当您想要获得最大年龄的实际学生时,基于解决方案变得更有趣,例如
Collection<Student> result = students.stream().collect(
Collectors.groupingBy(Student::getCity, Collectors.reducing(null, BinaryOperator.maxBy(
Comparator.nullsFirst(Comparator.comparingInt(Student::getAge)))))
).values();
well, actually, even this can also be expressed using the toMap
collector:
好吧,实际上,甚至这也可以使用toMap
收集器来表示:
Collection<Student> result = students.stream().collect(
Collectors.toMap(Student::getCity, Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Student::getAge)))
).values();
You can express almost everything with both collectors, but groupingBy
has the advantage on its side when you want to perform a mutable reductionon the values.
您可以使用两个收集器表达几乎所有内容,但是groupingBy
当您想要对值执行可变减少时,它具有优势。
回答by Alexis C.
The second approach calls get()
on an Optional
; this is usually a bad idea as you don't know if the optional will be empty or not (use orElse()
, orElseGet()
, orElseThrow()
methods instead). While you might argue that in this case there always be a value since you generate the values from the student list itself, this is something to keep in mind.
第二种方法需要get()
一个Optional
; 这通常是一个坏主意,因为您不知道 optional 是否为空(使用orElse()
, orElseGet()
,orElseThrow()
方法代替)。虽然您可能会争辩说,在这种情况下总是有一个值,因为您从学生列表本身生成值,但这是需要记住的。
Based on that, you might turn the variation 2 into:
基于此,您可以将变体 2 转换为:
final Collection<Integer> variation2 =
students.stream()
.collect(collectingAndThen(groupingBy(Student::getCity,
collectingAndThen(
mapping(Student::getAge, maxBy(naturalOrder())),
Optional::get)),
Map::values));
Although it really starts to be difficult to read, I'll probably use the variant 1:
虽然它确实开始难以阅读,但我可能会使用变体 1:
final List<Integer> variation1 =
students.stream()
.collect(groupingBy(Student::getCity,
mapping(Student::getAge, maxBy(naturalOrder()))))
.values()
.stream()
.map(Optional::get)
.collect(toList());
回答by Raghu K Nair
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);
}
}
回答by Surabhi
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))));