Say I have the following collection of Student
objects which consist of Name(String), Age(int) and City(String).
I am trying to use Java's Stream API to achieve the following sql-like behavior:
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.
Is there any other way to achieve this required behavior?
These methods remind me of over partition by
sql statements...
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.
As addition to Tagir’s great answer using
toMap
instead ofgroupingBy
, here the short solution, if you want to stick togroupingBy
:Note that this three arg
reducing
collector already performs a mapping operation, so we don’t need to nest it with amapping
collector, further, providing an identity value avoids dealing withOptional
. 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.Still, I think Tagir’s
toMap
based solution is preferable in this scenario.The
groupingBy
based solution becomes more interesting when you want to get the actual students having the maximum age, e.gwell, actually, even this can also be expressed using the
toMap
collector:You can express almost everything with both collectors, but
groupingBy
has the advantage on its side when you want to perform a mutable reduction on the values.Do not always stick with
groupingBy
. SometimestoMap
is the thing you need: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.The second approach calls
get()
on anOptional
; this is usually a bad idea as you don't know if the optional will be empty or not (useorElse()
,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.Based on that, you might turn the variation 2 into:
Although it really starts to be difficult to read, I'll probably use the variant 1: