Java8のStream APIの使い方(Streamの生成編)

スポンサーリンク

Java8から追加された機能の Stream API を使用するとコレクションや配列の複数の値に対して、様々な繰り返し処理を行えるようになりました。これに伴い、見易く不具合の少ないコードを記述できるようになっています。今回は Stream 自体の生成に関してまとめたいと思います。

Stream API の概要

Stream API を使用するにあたっては大きく2つの操作に分類されます。

  1. 中間操作
    Streamのデータを加工してStreamで返す。
  2. 終端操作
    Streamのデータを別のデータに変換する。
    forEachに関しては戻り値を返さないため副作用を起こすための終端操作となる。

基本的には「データを Stream に変換→中間操作で加工→終端操作で新しいデータを生成」という使い方になります。 これにより従来のfor文で記述してた処理の大半は Stream API で置き換える事が可能になりましたが、今までと同じ感覚で置き換えようとすると無理が生じてくるので Stream API やラムダ式の特性を理解する事が重要です。「外部イテレータから内部イテレータへ」という言葉が非常にしっくりきます。

生成処理について

Stream を扱うためにはここから始まります。Stream はインターフェースなので new でインスタンスを生成することが出来ないため、Streamを生成するメソッドを使用する必要があります。尚、Stream は参照型のオブジェクトを対象としているため、プリミティブ型を使用する場合は専用のIntStreamなどを使用してください。

CollectionのStream生成

Java8から Collection インターフェースのデフォルトメソッドとしてstream()parallelStream()が追加されました。Collection を実装しているクラス(List・Set・Queue等)の場合はこのメソッドを使用して Stream を生成する事が可能です。

List<String> lists = Arrays.asList("hoge", "fuga", "bar");
Stream<String> stream = lists.stream();   // 直列ストリーム
Stream<String> pstream = lists.parallelStream();   // 並列ストリーム

Stream の生成をparallesStream()にするだけで並列処理が実現できるようになっています。

ただし、これは Stream の実行プロパティのひとつであるため、生成された Stream に対してBaseStream#sequentialを実行すると直列ストリームに、BaseStream#parallelを実行すると並列ストリームに変更することが可能です。実行モードを確認するにはBaseStream#isParallelを使用します。

配列のStream生成

配列の Stream を生成する場合はArrays#streamを使用します。引数には配列を指定してください。配列からは直列ストリームの生成しかで出来ないので、並列ストリームにしたい場合は一度ストリームを生成してBaseStream#parallelを使用します。

Stream<String> astream = Arrays.stream(new String[] {"hoge", "fuga"});   // 直列ストリーム
Stream<String> pstream = astream.parallel();   // 並列ストリーム

MapのStream生成

Map から Stream を生成する場合は一度 Collection に変換します。

Stream<Map.Entry<Integer, String>> astream = map.entrySet().stream();   // 直列ストリーム
Stream<Map.Entry<Integer, String>> pstream = map.entrySet().parallelStream();   // 並列ストリーム
Stream<Integer> kstream = map.keySet().stream();   // KEYのみ
Stream<String> vstream = map.values().stream();   // VALUEのみ

テキストデータ等の読み込み

BufferedReader や Files にlines()メソッドが追加され、Stream を生成できるようになりました。レコードの終了判定が不要になります。

try (BufferedReader br = Files.newBufferedReader(Paths.get("test.txt"))) {
    br.lines().forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

try (Stream<String> fst = Files.lines(Paths.get("test.txt"))) {
    fst.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

Streamインターフェースのメソッドを使用して生成

Streamインターフェースには Stream インスタンスを生成するための様々な static メソッドが用意されています。

空のストリームを生成。

Stream<String> stream = Stream.empty();

初期値を指定して生成する。Stream#ofは可変長引数です。

Stream<String> stream = Stream.of("hoge", "fuga", "bar", "foo");

Builderを使用して生成。

Builder<String> builder = Stream.builder();
Stream<String> stream = builder.add("hoge").add("fuga").build();

Stream#iterateは第1引数に初期値、第2引数に関数型インターフェースの UnaryOperator が必要。第1引数に指定した値を元に生成していく。limit()を使用しないと無限に生成されるので注意。

Stream<Integer> stream = Stream.iterate(1, i -> i + i).limit(5);
stream.forEach(System.out::println);

// 実行結果
1
2
4
8
16

Stream#generateは関数型インターフェースの Supplier を引数に取る。Supplier は指定した値を返す関数。こちらもlimit()を使用しないと無限に生成される。

Stream<String> stream = Stream.generate(() -> "test").limit(5);
stream.forEach(System.out::println);

// 実行結果
test
test
test
test
test

複数の Stream を結合できる。違う型の Stream を結合しようとするとコンパイルエラーになる。

Stream<String> st1 = Stream.of("hoge", "fuga");
Stream<String> st2 = Stream.of("bar", "foo");
Stream<String> newStream = Stream.concat(st1, st2);

プリミティブ型のStream

プリミティブ型の Stream はIntStream LongStream DoubleStreamの3種類です。基本的に上記 Stream インターフェースと同様のメソッドが用意されています。配列の Stream は生成可能ですが、Collection の場合はそもそもプリミティブ型を指定できないため対象外になります。

IntStream intstream = Arrays.stream(new int[] {1, 2, 3});

IntStreamLongStreamは数字を範囲指定して生成が可能です。

IntStream intStream = IntStream.range(3, 6);
intStream.forEach(System.out::println);

// 実行結果、指定した範囲の末尾を含まないStreamを生成
3
4
5

IntStream intStream = IntStream.rangeClosed(3, 6);
intStream.forEach(System.out::println);

// 実行結果、指定した範囲の末尾を含むStreamを生成
3
4
5
6

関連記事

 Java8のforEachを使った繰り返し処理について - TASK NOTES

 Java8ラムダ式の使い方の基本 - TASK NOTES

 Java8のStream APIの使い方(中間操作編① - filter, map)

 Java8のStream APIの使い方(中間操作編② - flatMap, distinct, limit, skip)

 Java8のStream APIの使い方(中間操作編③ - sorted, peek)

 Java8のStream APIの使い方(終端操作編① - anyMatch, allMatch, noneMatch) - TASK NOTES