web-dev-qa-db-ja.com

Java 8 Stream API-Collectors.groupingBy(..)の後の値のみを選択

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ステートメントを思い出させます。

ありがとう


編集:以下のすべての答えは本当に興味深いものでしたが、残念ながら、これは私が探していたものではありません。私が取得しようとしているのは値だけだからです。キーは必要ありません。値だけが必要です。

8
Ghost93

常にgroupingByに固執しないでください。 toMapが必要な場合があります。

Collection<Integer> result = students.stream()
    .collect(Collectors.toMap(Student::getCity, Student::getAge, Integer::max))
    .values();

ここでは、キーが都市で値が年齢であるMapを作成するだけです。複数の学生が同じ都市にいる場合、ここで最大年齢を選択するだけのマージ機能が使用されます。それはより速くそしてよりきれいです。

29
Tagir Valeev

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の方が有利です。

15
Holger

2番目のアプローチは、Optionalget()を呼び出します。オプションが空になるかどうかわからないため、これは通常は悪い考えです(代わりに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());
1
Alexis C.
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);
    }
}
0
Raghu K Nair