Stream API 中間操作の sorted と peek について使い方をまとめました。
sorted:ソート
① 引数:なし / 戻り値:Stream<R>
② 引数:Comparator<T> / 戻り値:Stream<R>
sortedメソッドは2種類あり、①の場合は保持されてるデータがjava.lang.Comparable
を実装してる必要があります。Comparable を実装してるクラスは Java Platform SE 8 で確認して下さい。
②の場合は関数型インターフェースのComparator<T>
を渡してやる事で、ソート順を制御でき、Comparableを実装してないクラスでもソートする事が可能です。
Comparator<T>
は比較を行う関数型インターフェースであり、実装が必要なメソッドはint compare(T o1, T o2)
で引数を2つ受け取り、intを返します。
sortedの使い方
Integerはjava.lang.Comparable
を実装しているため引数なしでソート可能です。
List<Integer> lists = Arrays.asList(300, 100, 500, 200, 400); lists.stream().sorted().forEach(System.out::print); // 実行結果 100200300400500
リストに格納したPersonというクラスを年齢順ソートします。
public class Person { private String name; private String gender; private Integer age; public Person(String name, String gender, Integer age) { this.name = name; this.gender = gender; this.age = age; } public String getName() { return name; } public String getGender() { return gender; } public Integer getAge() { return age; } public String toString() { return name + ", " + gender + ", " + age; } public static void main(String[] args) { List<Person> persons = new ArrayList<>(); persons.add(new Person("田中太郎", "male", 35)); persons.add(new Person("山田一郎", "male", 22)); persons.add(new Person("鈴木花子", "famale", 19)); // ラムダ式 - 年齢でソート persons.stream().sorted((person1, person2) -> person1.getAge().compareTo(person2.getAge())) .forEach(Person::.toString); } } // 実行結果 鈴木花子, famale, 19 山田一郎, male, 22 田中太郎, male, 35
匿名クラスで記述した場合です。
// 匿名クラス - 名前でソート persons.stream().sorted(new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getName().compareTo(o2.getName()); } }).forEach(person -> System.out.println(person.toString())); // 実行結果 山田一郎, male, 22 田中太郎, male, 35 鈴木花子, famale, 19
Stream以外のsortメソッド
Java8からはList#sort
メソッドが追加されたため Stream に変換しなくてもソートする事は可能です。引数にはComparator<T>
を渡す必要があります。
- List#sort(Comparator<? super E> c)
- Collections.sort(List
list) - Collections.sort(List
list, Comparator<? super T> c) - Arrays.sort(T[] a, Comparator<? super T> c)
TreeMap のコンストラクタで Comparator を渡すとキーの並び順を指定できます。
Map<String, String> maps = new TreeMap<>((k1, k2) -> k1.length() - k2.length());
Comparatorのdefaultメソッド
Comparatorのデフォルトメソッドは以下の通りです。
メソッド | 引数 | 戻り値 |
---|---|---|
naturalOrder | なし | Comparator<T> |
reverseOrder | なし | Comparator<T> |
reversed | なし | Comparator<T> |
nullsFirst | Comparator<T> | Comparator<T> |
nullsLast | Comparator<T> | Comparator<T> |
comparing | Function<T, U> | Comparator<T> |
comparing | Function<T, U>, Comparator<U> | Comparator<T> |
comparingInt | ToIntFunction<T> | Comparator<T> |
comparingLong | ToLongFunction<T> | Comparator<T> |
comparingDouble | ToDoubleFunction<T> | Comparator<T> |
thenComparing | Comparator<T> | Comparator<T> |
thenComparing | Function<T, U> | Comparator<T> |
thenComparing | Function<T, U>, Comparator<U> | Comparator<T> |
thenComparingInt | ToIntFunction<T> | Comparator<T> |
thenComparingLong | ToLongFunction<T> | Comparator<T> |
thenComparingDouble | ToDoubleFunction<T> | Comparator<T> |
ソート順
ソート対象オブジェクトのクラスがComparableインターフェースを実装している場合、並び順の指定は以下のように行います。
自然順の場合は、Comparator#naturalOrder
を使用。(実装されたComparableインターフェースを使った並び順)
lists.stream().sorted(Comparator.naturalOrder()).forEach(System.out::print); // 実行結果 100200300400500
自然順の逆順でソートする場合は、Comparator#reverseOrder
を使用。
lists.stream().sorted(Comparator.reverseOrder()).forEach(System.out::print); // 実行結果 500400300200100
Comparableインターフェースを実装していないオブジェクトを逆順でソートする場合は、Comparator#reversed
を使用。
Comparator<LamdaSample> c = (person1, person2) -> person1.getAge().compareTo(person2.getAge()); persons.stream().sorted(c.reversed()).forEach(person -> System.out.println(person.toString())); // 実行結果 田中太郎, male, 35 山田一郎, male, 22 鈴木花子, famale, 19
Nullを含むソート
ソート対象のオブジェクトにNullが含まれる場合、NullPointerException が発生してしまいす。
List<String> strs = Arrays.asList("[hoge]", "[fuga]", null, "[bar]", "[foo]"); strs.stream().sorted().forEach(System.out::println); // NullPointerExceptionが発生
このような場合はComparator#nullsFirest
やComparator#nullsLast
を使用するとNullを含めたソートをすることが可能です。
strs.stream().sorted(Comparator.nullsFirst((s1, s2) -> s1.compareTo(s2))).forEach(System.out::print); strs.stream().sorted(Comparator.nullsLast((s1, s2) -> s1.compareTo(s2))).forEach(System.out::print); // 実行結果 null[bar][foo][fuga][hoge] [bar][foo][fuga][hoge]null
キー項目を指定してソート
上述したサンプルにある Person というクラスをソートする場合、ソートするキー項目を指定して次のように記述する必要がありました。
persons.stream().sorted((person1, person2) -> person1.getAge().compareTo(person2.getAge())).forEach(person -> System.out.println(person.toString()));
これでは冗長で可読性も悪く毎回書くのも面倒です。こんな時にComparator#comparing
を使用することでスッキリと記述する事が可能です。comparing は Function<T, U> を引数に取り、キー項目を抽出する関数を渡すとキー項目で比較する Comparator を返します。ただし、この場合Comparable#compareTo
を使用した Comparator を返すため、キー項目はComparableインターフェースを実装している必要があります。
// ラムダ式 persons.stream().sorted(Comparator.comparing(person -> person.getAge())).forEach(LamdaSample::toString); // メソッド参照 persons.stream().sorted(Comparator.comparing(Person::getAge)).forEach(person -> System.out.println(person.toString())); // 実行結果 鈴木花子, famale, 19 山田一郎, male, 22 田中太郎, male, 35
キー項目比較用のComparatorを渡す事も出来ます。
persons.stream().sorted(Comparator.comparing(Person::getAge, Comparator.reverseOrder())).forEach(person -> System.out.println(person.toString()));
キー項目がプリミティブ型の場合は、comparingInt
comparingLong
comparingDouble
を使用しましょう。
複数のソートキーを指定
Comparator#thenComparing
を使用して複数の Comparator を使用する事も可能です。今回のソート対象は以下のクラスを使用します。
public class Staff { private String name; private String gender; private int age; private int salary; public Staff(String name, String gender, int age, int salary) { this.name = name; this.gender = gender; this.age = age; this.salary = salary; } public String getName() { return name; } public String getGender() { return gender; } public int getAge() { return age; } public int getSalary() { return salary; } public String toString() { return name + ", " + gender + ", " + age + ", " + salary; } }
次の例はSalary:降順、Age:降順、Name:昇順
で並べ替えています。
List<Staff> staffs = new ArrayList<>(); staffs.add(new Staff("Michael", "male", 35, 400000)); staffs.add(new Staff("Jonson", "male", 35, 400000)); staffs.add(new Staff("William", "male", 23, 300000)); staffs.add(new Staff("Angelina", "famale", 29, 300000)); staffs.stream().sorted(Comparator.comparingInt(Staff::getSalary).reversed() .thenComparing(Comparator.comparingInt(Staff::getAge).reversed()) .thenComparing(Comparator.comparing(Staff::getName))) .forEach(staff -> System.out.println(staff.toString())); // 実行結果 Jonson, male, 35, 400000 Michael, male, 35, 400000 Angelina, famale, 29, 300000 William, male, 23, 300000
thenComparing
にもキー項目を抽出する関数を渡す事や、プリミティブ型で抽出する関数を渡すメソッドも用意されています。
peek:特殊
引数:Consumer
peekメソッドはStreamの状態を変えずにそのままStreamを返します。値を返さない Consumer<T> を引数に取るので forEach のようなイメージですが、peekメソッドは中間操作です。途中の状態を確認したりデバッグ目的で使用します。
peekの使い方
300未満の数値を filter で抽出して最後に数値の個数をカウントする処理で、抽出した結果を表示しています。
List<Integer> lists2 = Arrays.asList(333, 111, 555, 222, 444); Long result = lists2.stream().filter(i -> i < 300).peek(t -> System.out.println("peek:" + t)).count(); System.out.println(result); // 実行結果 peek:111 peek:222 2
関連記事
Java8のforEachを使った繰り返し処理について - TASK NOTES
Java8のStream APIの使い方(Streamの生成編)
Java8のStream APIの使い方(中間操作編① - filter, map)
Java8のStream APIの使い方(中間操作編② - flatMap, distinct, limit, skip)
Java8のStream APIの使い方(終端操作編① - anyMatch, allMatch, noneMatch) - TASK NOTES