【Java】Listをソートする方法まとめ|基本・複数条件・null対応・降順も解説

当ページのリンクには広告が含まれています。

JavaのStreamでソートをしたいんだけど、どうやるのかパッとコードが出てこないということはありませんか?

本記事ではStreamでソートする様々なケースを考慮してサンプルを掲載していますのでぜひ参考にして下さい!

※Java11を使っています。

目次

基本的な sorted() の使い方

List<String>のソート

Stringの場合、アルファベットを元にソートすることができます。

降順ソートにしたい場合はsortedの引数にComparator.reverseOrder()を与えます。(昇順/降順を参照)

List<Integer>のソート

intの場合(実際にはIntegerで保持することになりますが)、数値としてソートされます。

昇順/降順

昇順では特に指定する必要はありませんが、明示的にComparator.naturalOrder()を与えることもできます。

降順の場合、Comparator.reverseOrder()をsortedメソッドの引数に与えます。

複数条件

複数条件を指定したい場合、thenComparingを追加していきます。

最初に指定した条件から優先的に適用されるので、以下の例では [ 年齢 > 名前 ] の順でソートされています。

null値を含む場合の扱い

nullが含まれるキーをソートする場合、Comparator.nullsFirst または Comparator.nullsLast を使用する必要があります。

Comparator.nullsFirstはnullが最初になるようにソートし、Comparator.nullsLastは最後になるようにソートします。

使用しなかった場合は NullPinterException がスローされるので注意が必要です。

独自クラスのソート方法

Comparableを実装する

ソート対象のクラスにComparableが実装してあれば、OverrideされたcompareToに従って比較が行われることでソート順が規定されます。

compareToの比較をソート順としたい場合、sortedメソッドに追加の条件は必要はありません。

以下の例では、scoreがcompareToメソッドで比較されています。

Comparatorを指定する

Comparatorを使用して対象クラスのフィールドでソートすることもできます。

以下の例ではscoreの降順でソートしています。

ちなみに、対象クラスにComparableが実装されている場合でも、Comparatorの指定が優先されるので注意してください。

import java.util.List;
import java.util.Comparator;
import static java.util.stream.Collectors.toList;
public class ComparatorSortExample {
  public static void main(String[] args) {
    List<Student> students = List.of(
        new Student("Alice", 90),
        new Student("Bob", 75),
        new Student("Charlie", 82)
    );
    List<Student> sortedStudents = students.stream()
        .sorted(Comparator.comparing(Student::getScore).reversed())
        .collect(toList());
    sortedStudents.forEach(
        student -> System.out.println(student.getName() + " - " + student.getScore()));
  }
  static class Student implements Comparable<Student> {
    private String name;
    private int score;
    public Student(String name, int score) {
      this.name = name;
      this.score = score;
    }
    public String getName() {
      return name;
    }
    public int getScore() {
      return score;
    }
    @Override
    public int compareTo(Student other) {
      return Integer.compare(this.score, other.score);
    }
  }
}
// Alice - 90
// Charlie - 82
// Bob - 75

Mapをソートする

Mapのソートではkeyまたはvalueのいずれでソートするかを指定します。

以下の例ではvalueで昇順ソートしていますが、Map.Entry.comparingByKey() を使用することでkeyによるソートが可能です。

import java.util.*;
import static java.util.stream.Collectors.toMap;
public class MapSortExample {
  public static void main(String[] args) {
    Map<String, Integer> unsortedMap = new HashMap<>();
    unsortedMap.put("Alice", 90);
    unsortedMap.put("Bob", 75);
    unsortedMap.put("Charlie", 82);
    Map<String, Integer> sortedMap = unsortedMap.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .collect(toMap(
            Map.Entry::getKey,
            Map.Entry::getValue,
            (e1, e2) -> e1,
            LinkedHashMap::new));
    sortedMap.forEach(
        (key, value) -> System.out.println(key + " - " + value));
  }
}
// Bob - 75
// Charlie - 82
// Alice - 90

Stream.sorted()以外のソート方法

Javaのリストをソートする方法はいくつかありますが、特に Stream.sorted()Collections.sort() の違いや、 parallelStream().sorted() を使った場合のパフォーマンスについて解説します。

Collections.sort() と Stream.sorted() の違い

Collections.sort()

  • 直接リストを変更する 破壊的な ソート
  • List<T> のメソッドではなく、Collections クラスの静的メソッドとして提供。
  • 内部的に TimSort (計算量 O(n log n)) を使用

Stream.sorted()

  • 非破壊的な ソートで、新しいストリームを返す
  • Stream<T> のメソッドとして提供

実装例

// 出力結果
Collections.sort(): [1, 2, 3, 5, 8] // 元のリストの並び順が変ってしまう
Stream.sorted(): [1, 2, 3, 5, 8]
streamNumbers: [5, 3, 8, 1, 2]      // Streamだと変わらない

parallelStream().sorted() とのパフォーマンス比較

  • parallelStream() を使うと、複数のスレッドで並列処理される可能性がある。
  • しかし、中間結果を最終的に結合してソートするため並列処理としてのパフォーマンス向上を得られないこともある。
  • データ量や構造にも依存するので、ベンチマークを取って採用するかを検討した方がいい。

実装例

100万個の整数をソートするケース

// 出力結果
sorted : 543 ms
parallel sorted : 296 ms
ここでのまとめ
  • 既存のリストを変更したいならCollections.sort()
  • 非破壊的に処理をしたいなら Stream.sorted()
  • 高パフォーマンスを得たいならparallelStream().sorted()を試す

Stream.sorted() の時間計算量

  • 時間計算量: O(n log n)
  • 最終的に Arrays.sort(T[]) を使用し、オブジェクト型 (T[]) のソートでは TimSort が適用される。

まとめ

  • 基本的なソートはStreamのソートではsortedという中間操作を行う。
  • 独自クラスをソートする場合は、Comparableを実装するか、Comparatorを指定する
  • ソートには Collections.sort() と parallelStream.sorted() もあるので、用途に合わせて使うと良い。

最後までお読み頂き、ありがとうございました!
ご意見・ご要望がありましたら、遠慮なくコメント下さい!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

リーマンショックの影響で26歳の時にIT業界から離れ、紆余曲折を経て34歳でエンジニアに復帰。
現在はフリーランスエンジニア兼コアファクトリ合同会社代表。
得意な言語はJava。

新人教育経験あり(わからなくて進まない子を放置しない方針)
Javaの新人教育にお困りでしたらお声がけください。

■保有資格
・Java Gold SE 11

コメント

コメントする

CAPTCHA


目次