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

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

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

# Java Stream API
[Stream (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html)

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

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


# 複数条件によるソート

[Comparator](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Comparator.html)インタフェースを使うことによって可能。

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


# サンプルコード
設計の雑さについてはご容赦。

## `Article`クラス
```java: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`クラス
```java: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)](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html#sorted-java.util.Comparator-)

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

## 単項目でのソート
辞書的に名前順でソート。

```java: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)](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Comparator.html#thenComparing-java.util.Comparator-)

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

```java:comparator
Comparator.comparing(...).thenComparing(...).thenComparing(...);
```

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

## 複数項目でのソート
### 名前 -> 投稿日でソート
```java: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
```

### 名前 -> いいね数でソート
```java: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()`をつける

```java: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 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Stream.html)
[Comparator (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/Comparator.html)