For example, suppose a user and the amount paid by that user are given in the following form:
public class Payment {
public static void main(String[] args) {
var payments = List.of(
new Payment("A", 10),
new Payment("B", 20),
new Payment("B", 30),
new Payment("C", 40),
new Payment("C", 50),
new Payment("C", 60)
);
}
private String name;
private int value;
public Payment(String name, int value) {
this.name = name;
this.value = value;
}
public String getName() { return name; }
public int getValue() { return value; }
}
Now, for each user, we want to find the number of payments, the total amount paid, or the maximum amount. In SQL, it seems that you can easily find it by combining GROUP BY
and window function, but in Java Stream, how should you write it?
select name, count(*) from payment group by name;
select name, sum(value) from payment group by name;
select name, max(value) from payment group by name;
The overall policy is to use Collectors.groupingBy
. First, the number of payments, that is, group-by-count
.
var counts = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.counting()));
counts.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).forEach(System.out::println);
// A=1
// B=2
// C=3
There is a well-known method called Collectors.counting
, so it seems good to use it. The total amount paid next. The point is group-by-sum
, but this also has a method with a descriptive name of Collectors.summingInt
, so just use this.
var sums = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.summingInt(Payment::getValue)));
sums.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).forEach(System.out::println);
// A=10
// B=50
// C=150
Finally, "maximum amount paid" = group-by-max
, but I personally feel that it is the most controversial. As a basic policy, it seems to be quick to use Collectors.maxBy
.
var maxs = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.maxBy(Comparator.comparingInt(Payment::getValue))));
maxs.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().get().getValue()).forEach(System.out::println);
// A=10
// B=30
// C=60
At this time, the type of the variable maxs
isMap <String, Optional <Payment >>
. ʻOptional is like a marker to remind you that it may be null, but here in business logic the value of
maxs cannot be null. In short, ʻOptional
here doesn't make much sense, so I want to get rid of it. In other words, I want to set the type of maxs
toMap <String, Payment>
, but in such a case, it seems to be quick to do as follows.
var maxs = payments.stream().collect(Collectors.groupingBy(Payment::getName, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparing(Payment::getValue)), Optional::get)));
maxs.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue().getValue()).forEach(System.out::println);
// A=10
// B=30
// C=60
However, at this point, the feeling of black magic begins to be good, so I want to moderate it (´ ・ ω ・ `)
Recommended Posts