Name(String)、Age(int)、City(String)で構成される次のStudent
オブジェクトのコレクションがあるとします。
JavaのStreamAPIを使用して、次のSQLのような動作を実現しようとしています。
_SELECT MAX(age)
FROM Students
GROUP BY city
_
今、私はそうするための2つの異なる方法を見つけました:
_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());
_
そしてもう1つ:
_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ステートメントを思い出させます。
ありがとう
編集:以下のすべての答えは本当に興味深いものでしたが、残念ながら、これは私が探していたものではありません。私が取得しようとしているのは値だけだからです。キーは必要ありません。値だけが必要です。
常にgroupingBy
に固執しないでください。 toMap
が必要な場合があります。
Collection<Integer> result = students.stream()
.collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
.values();
ここでは、キーが都市で値が年齢であるMap
を作成するだけです。複数の学生が同じ都市にいる場合、ここで最大年齢を選択するだけのマージ機能が使用されます。それはより速くそしてよりきれいです。
Tagirのすばらしい答えtoMap
の代わりにgroupingBy
を使用することに加えて、groupingBy
に固執したい場合は、ここで簡単な解決策を示します。
Collection<Integer> result = students.stream()
.collect(Collectors.groupingBy(Student::getCity,
Collectors.reducing(-1, Student::getAge, Integer::max)))
.values();
この3つの引数reducing
コレクターはすでにマッピング操作を実行しているため、mapping
コレクターとネストする必要はありません。さらに、ID値を指定すると、Optional
。年齢は常に正であるため、-1
で十分であり、グループには常に少なくとも1つの要素があるため、結果としてID値が表示されることはありません。
それでも、このシナリオでは、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
の方が有利です。
2番目のアプローチは、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);
}
}