Java
StreamAPI
新人プログラマ応援

JavaのStreamを使って複数条件でソートする

最近、業務で半年ぶりくらいにJavaを書いている。
以前業務で使っていたのはJava7(!)だったのだが、現在Java8を使用している。

streamを結構な頻度で使用しており、先日複数条件でソートできることを知ったため書き留め。

Java Stream API

Stream (Java Platform SE 8 )

反復処理や並行処理等、集約操作をサポートするAPI。

ラムダ式(JSでいうアロー関数)と組み合わせることで、拡張for構文等を使うことなくシンプルに記述できる。

複数条件によるソート

Comparatorインタフェースを使うことによって可能。

comparing()thenComparing()メソッドによって、ソート条件を指定し、streamsorted()に渡すことで、複数条件によるソートができる。

サンプルコード

設計の雑さについてはご容赦。

Articleクラス

Article.java
import java.time.LocalDate;

public class Article {

  /** 投稿者 */
  private String contributor;

  /** 投稿日 */
  private LocalDate date;

  /** いいね数 */
  private int goodCount;

  /** コンストラクタ */
  public Article(String contributor, LocalDate date, int goodCount) {
    this.contributor = contributor;
    this.date = date;
    this.goodCount = goodCount;
  }

  /** Getter, Setter */
  public String getContributor() {
    return this.contributor;
  }

  public void setContributor(String contributor) {
    this.contributor = contributor;
  }

  public LocalDate getDate() {
    return this.date;
  }

  public void setDate(LocalDate date) {
    this.date = date;
  }

  public int getGoodCount() {
    return this.goodCount;
  }

  public void setGoodCount(int goodCount) {
    this.goodCount = goodCount;
  }

}

Streamクラス

Stream.java
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

public class Stream {
  public static void main(String[] args) {
    // データ生成
    Article a1 = new Article("Taro", LocalDate.of(2018, 7, 11), 15);
    Article a2 = new Article("Taro", LocalDate.of(2015, 11, 1), 53);
    Article a3 = new Article("Taro", LocalDate.of(2015, 10, 31), 54);

    Article a4 = new Article("Hanako", LocalDate.of(2017, 2, 5), 53);
    Article a5 = new Article("Hanako", LocalDate.of(2016, 11, 1), 160);
    Article a6 = new Article("Hanako", LocalDate.of(2012, 8, 20), 22);

    Article a7 = new Article("Pochi", LocalDate.of(2013, 3, 16), 38);
    Article a8 = new Article("Pochi", LocalDate.of(2011, 9, 25), 200);
    Article a9 = new Article("Pochi", LocalDate.of(2018, 4, 13), 10);

    // リスト作成
    List<Article> list = Arrays.asList(a1, a2, a3, a4, a5, a6, a7, a8, a9);
    list.stream().forEach(a ->
      System.out.println(a.getContributor() + " " + a.getDate() + " " + a.getGoodCount()));

  }
}

とりあえず、a1からa9をリストにして出力すると

出力結果
Taro 2018-07-11 15
Taro 2015-11-01 53
Taro 2015-10-31 54
Hanako 2017-02-05 53
Hanako 2016-11-01 160
Hanako 2012-08-20 22
Pochi 2013-03-16 38
Pochi 2011-09-25 200
Pochi 2018-04-13 10

となる。

ソート処理

sorted (Java Platform SE 8)

Comparatorを引数に渡し、指定した順序でソートする。

単項目でのソート

辞書的に名前順でソート。

Stream.java
// Comparator作成
Comparator<Article> comparator = Comparator.comparing(Article::getContributor);
// 投稿者名でソート
list.stream().sorted(comparator)
  .forEach(a ->
    System.out.println(a.getContributor() + " " + a.getDate() + " " + a.getGoodCount()));
出力結果
Hanako 2017-02-05 53
Hanako 2016-11-01 160
Hanako 2012-08-20 22
Pochi 2013-03-16 38
Pochi 2011-09-25 200
Pochi 2018-04-13 10
Taro 2018-07-11 15
Taro 2015-11-01 53
Taro 2015-10-31 54

他の項目でもソート可能。

thenComparing

thenComparing (Java Platform SE 8)

比較条件をチェーンできるメソッド。
以下のように使用する。

comparator
Comparator.comparing(...).thenComparing(...).thenComparing(...);

前の条件が同じ(equals)だった場合に、更に比較を行う。

辞書式順序コンパレータをもう一方のコンパレータとともに返します。
このComparatorで2つの要素が等しい(つまり、compare(a, b) == 0)と見なされる場合は、otherを使ってその順序が決められます。

複数項目でのソート

名前 -> 投稿日でソート

Stream.java
// Comparator作成
Comparator<Article> comparator =
  Comparator.comparing(Article::getContributor).thenComparing(Article::getDate);
// ソート処理
list.stream().sorted(comparator)
  .forEach(a ->
    System.out.println(a.getContributor() + " " + a.getDate() + " " + a.getGoodCount()));
出力結果
Hanako 2012-08-20 22
Hanako 2016-11-01 160
Hanako 2017-02-05 53
Pochi 2011-09-25 200
Pochi 2013-03-16 38
Pochi 2018-04-13 10
Taro 2015-10-31 54
Taro 2015-11-01 53
Taro 2018-07-11 15

名前 -> いいね数でソート

Stream.java
// Comparator作成
Comparator<Article> comparator =
  Comparator.comparing(Article::getContributor).thenComparing(Article::getGoodCount);
// ソート処理
list.stream().sorted(comparator)
  .forEach(a ->
    System.out.println(a.getContributor() + " " + a.getDate() + " " + a.getGoodCount()));
出力結果
Hanako 2012-08-20 22
Hanako 2017-02-05 53
Hanako 2016-11-01 160
Pochi 2018-04-13 10
Pochi 2013-03-16 38
Pochi 2011-09-25 200
Taro 2018-07-11 15
Taro 2015-11-01 53
Taro 2015-10-31 54

昇順にしたい場合は、reversed()をつける

Stream.java
// Comparator作成
Comparator<Article> comparator =
  Comparator.comparing(Article::getContributor).thenComparing(Article::getGoodCount).reversed();
// ソート処理
list.stream().sorted(comparator)
  .forEach(a ->
    System.out.println(a.getContributor() + " " + a.getDate() + " " + a.getGoodCount()));
出力結果
Taro 2015-10-31 54
Taro 2015-11-01 53
Taro 2018-07-11 15
Pochi 2011-09-25 200
Pochi 2013-03-16 38
Pochi 2018-04-13 10
Hanako 2016-11-01 160
Hanako 2017-02-05 53
Hanako 2012-08-20 22

所感

  • 複数条件でのソートは意外と楽だった
  • Streamめちゃくちゃ便利(今更だけど)
  • Comparatorの使い方はまだまだ学ぶ必要がありそう
  • 昇順・降順等を細かく指定したい場合は、Comparator自体を分けて複数回ソートした方が良さそう

参考

Stream (Java Platform SE 8 )
Comparator (Java Platform SE 8 )