Problem Statement
Given the following class (simplified for the question):
public static class Match {
private final String type;
private final int score;
public Match(String type, int score) {
this.type = type;
this.score = score;
}
public String getType() {
return type;
}
public int getScore() {
return score;
}
}
I have a Stream<Match>
that contains multiple instances of the class, the same type appears multiple times, but with different scores:
Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10),
new Match("B", 3), new Match("B", 6), new Match("B", 12),
new Match("C", 1));
I now want to collect the stream so that the result is a List<Match>
containing only the instances with the highest score of each type.
What I tried
The following code is working, but I am not sure if it is the "optimal" solution (aside from the horrible reading and formatting):
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Match::getType, Collectors.collectingAndThen(
Collectors.toList(),
l -> l.stream().max(Comparator.comparing(Match::getScore)).get())), Map::values))
.forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));
and:
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Match::getType, Collectors.maxBy(Comparator.comparing(Match::getScore))), Map::values))
.forEach(m -> m.ifPresent(ma -> System.out.println(ma.getType() + ": " + ma.getScore())));
Output (correct):
A: 10
B: 12
C: 1
Additionally I was not able to extract a generic, static method returning a collector so that I can simply use it where I need in a way like:
.collect(distinctMaxByProperty(Match::getType, Match::getScore)
Any help would be greatly appreciated!
I have two methods for you.
First Method: Collectors.toMap()
A possible implementation is to use
Collectors.toMap()
to answers your problem.And if you prefer to get a
List<Match>()
, you can remap the resultSecond Method: Custom Collector
Like you say in your question, it is possible to extract this logic in a custom collector. You can do it like this:
and use it like this:
Hope this help you :)
Don’t collect into a
List
, just to extract one value, when you can collect the maximum element in the first place, e.g.But whenever you encounter the necessity to extract an
Optional
in the context ofgroupingBy
, it’s worth checking whether toMap` with merge function can give a simpler result:Once you have the
Map
you can produce your desired output viaBut if you don’t need the actual
Match
instances, you can do it even simpler:Try to use a
TreeMap
for this :UPDATE
In case your
Match
class implementsComparable
. You can simplify this:to this: