下手の横好き

文系卒エンジニアのしがない技術ブログ

Javaを学ぶ -Java 8 Stream編-

背景

  • 業務で半年ぶりにJavaを触るので、言語レベルでの復習をしたい。
  • 公式のドキュメントに目を通しておきたい。
  • 自分の中のJavaは7で止まっている為、少しずつ新機能を学びたい。

Streamとは

Java SE 8から実装された、コレクション(配列、リスト等)を扱いやすくするAPI
参照: Stream (Java Platform SE 8)

それまで拡張for文等で煩雑になりがちだった、コレクションに対する操作を明快にしてくれる。

Streamでは同じくJava SE 8で実装された、これまた重要なラムダ式を用いている。
...が、今回はラムダ式に関する解説は省略する。後日また改めて記事でまとめる予定。

Streamの構造

Streamは以下のような構造を持つ。

collection.stream() // streamの取得     
          .filter() // 中間操作
          .sum();   // 終端操作

上記の例で解説していく。

  1. stream() ...streamの取得
    Collection.stream()メソッドによって、コレクションのストリームを作成する。 ここで作成したストリームに対して操作を行っていくことになる。

  2. filter() ...中間操作
    取得したストリームに対して、0個以上の中間操作を行う。
    代表的なものはfilter(Predicate)で、あるストリームを別のストリームに変換する。

  3. sum() ...終端操作
    0個以上の中間操作を行った後、1つの終端操作を行う。
    代表的なものにcount()sum(), forEach(Consumer)等がある。

Streamのサンプル

サンプル1: リスト内の奇数を合計し、出力する
  • Streamを使わない場合
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = 0;

for(Integer i : integerList) {
  if (i % 2 != 0) {
    sum += i;
  }
}

System.out.println(sum); // 25
  • Streamを使う場合
List<Integer> integerList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

Integer sum = integerList.stream()
                         .mapToInt(x -> x)
                         .filter(i -> i % 2 != 0)
                         .sum();

System.out.println(sum); // 25

Streamを用いた記述の方が圧倒的にシンプルな上に読みやすい。

サンプル2: 各要素を操作し、新しいコレクションを生成する
  • Streamを使わない場合
List<String> langs = Arrays.asList("Java", "Ruby", "Go", "Python");
List<String> langWithBlacket = new ArrayList<>();

for(String lang : langs) {
  langWithBlacket.add("[" + lang + "]");
}

System.out.println(langWithBlacket); // [[Java], [Ruby], [Go], [Python]]
  • Streamを使う場合
List<String> langs = Arrays.asList("Java", "Ruby", "Go", "Python");
System.out.println(langs.stream()
                        .map(s -> "[" + s + "]")
                        .collect(Collectors.toList()));
                        // [[Java], [Ruby], [Go], [Python]]

場合によっては新規の変数を準備する必要がなく、記述量が減る。

サンプル3: コレクション内の要素全てに判定をかける
  • コレクション内の要素に空白が含まれていないか判定
List<String> names = Arrays.asList("John", "Mark", "Andy", "Judy");    System.out.println(names.stream()
                            .allMatch(s -> !s.isEmpty()));
                            // true
  • コレクション内の要素に条件を満たすものがあるか判定
List<String> names = Arrays.asList("John", "Mark", "Andy", "Judy");
System.out.println(names.stream()
                            .anyMatch(s -> s.startsWith("A")));
                            // true

List<String> names = Arrays.asList("John", "Mark", "Andy", "Judy");
System.out.println(names.stream()
                            .anyMatch(s -> s.startsWith("K")));
                            // false

個人的にこの機能はものすごく便利。

他にも、ラムダ式の強みを活かしてインタフェースを扱うことも可能。

まとめ

煩雑になりがちだったコレクションに対する操作を、シンプルに記述出来るStreamは非常に便利で、扱う機会が多いため学習した。
特に、少ない記述量で中間操作をチェーン出来る点が非常に良いと感じた。
だいぶ前にリリースされたJava SE 8だが、ラムダ式とともにまだまだ学習が足りない為、継続的に学習していく。

参考

Stream (Java Platform SE 8)

[Java8] はじめて触るStreamの世界 | Developers.IO

Java8のStreamを使いこなす - きしだのはてな

Java Stream APIをいまさら入門 - Qiita