ストリームを使用してイテレートしていくには、まずストリームを生成する必要がある。
ストリームを生成するには複数の方法がありるが、大きくは、リストなど基になるものがある場合と、基になるものがない場合に分けられる。
●コレクションからストリームを取得
最もよく使用されるのが、コレクションからストリームを取得する方法となる。
ストリームを得るには、java.util.Collectionインタフェースに追加されたstreamメソッドを使用する。
コレクションではプリミティブ型の値は保持できないため、コレクションから取得できるストリームの型はStreamインタフェースとなる。
List<String> texts = ...;
Stream<String> stream = texts.stream();
●配列からストリームを取得
配列からストリームを取得するには、配列のユーティリティークラスであるjava.util.Arraysクラスのstreamメソッドを使用する。
・Stream stream(T[] array)
・Stream stream(T[] array, int start, int end)
・IntStream stream(int[] array)
・IntStream stream(int[] array, int start, int end)
・LongStream stream(long[] array)
・LongStream stream(long[] array, int start, int end)
・DoubleStream stream(double[] array)
・DoubleStream stream(double[] array, int start, int end)
int[] numbers = ...
// 配列の全要素をイテレートするストリームを取得
IntStream stream1 = Arrays.stream(numbers);
// 配列の1番目の要素から3番目の要素をイテレートするストリームを取得
IntStream stream2 = Arrays.stream(numbers, 0, 4);
String[] texts = ...;
// 配列の全要素をイテレートするストリームを取得
Stream<String> stream3 = Arrays.stream(texts);
// 配列の2番目の要素から4番目の要素をイテレートするストリームを取得
Stream<String> stream4 = Arrays.stream(texts, 2, 5);
●ファイルからストリームを取得
読み込んだファイルをストリームにするためのAPIも追加されています。
まず、java.io.BufferedReaderクラスのlinesメソッドです。
これはファイルを行単位でイテレートするストリームを取得できる。
たとえば、foo.txtファイルの内容を標準出力に出力するには次のようにする。
String filename = foo.txt;
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
// 読み込んだファイルをイテレートするストリームの取得
Stream<String> stream = reader.lines();
stream.forEach(line -> System.out.println(line));
} catch (IOException ex) {
// 例外処理
}
NIO.2でもストリームを取得することができる。
この場合、java.nio.file.Filesクラスのlinesメソッドを使用する。
引数で指定するjava.nio.file.Pathオブジェクトで指定したファイルのストリームを取得する。文字セットを指定することもできる。
String filename = foo.txt;
try {
Path path = Paths.get(filename);
// 読み込んだファイルをイテレートするストリームの取得
Stream<String> stream = Files.lines(path);
stream.forEach(line -> System.out.println(line));
} catch (IOException ex) {
// 例外処理
}
●要素を列挙してストリームを生成
要素を列挙して、ストリームを生成することができる。
そのためにはストリームのofメソッドを使用し、引数に要素を並べていく。ofメソッドの引数は可変長なので、必要な個数だけ要素を並べることができる。
// 文字列のストリームの生成
Stream<String> words = Stream.of(alpha, bravo, charie, delta);
// intのストリームの生成
IntStream numbers = IntStream.of(0, 1, 2);
●範囲を指定してストリームを生成
IntStreamインタフェースとLongStreamインタフェースは、範囲を指定してストリームを生成することができる。
範囲を指定してストリームを生成するにはrangeメソッドとrangeClosedメソッドを使用する。いずれも引数は2つで開始値と終端値を表す。
rangeメソッドとrangeClosedメソッドの違いは、終端値を含むか含まないかということで、rangeメソッドが終端値を含まず、rangeClosedメソッドが終端値を含む。
// 0から9までのストリームを生成
// rangeメソッドは第2引数の値を含まないため、9より1大きい値を使用する
IntStream nums1 = IntStream.range(0, 10);
// 0から9までのストリームを生成
// rangeClosedメソッドは第2引数の値を含む
IntStream nums2 = IntStream.rangeClosed(0, 9);
IntStreamインタフェースのrangeメソッドは、for文の代わりに使用することができる。
たとえば、0から9までの値を標準出力に出力するコードをfor文とrangeメソッドで記述すると次のようになる。
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
// for文と同様の処理をストリームで行う
IntStream.range(0, 10)
.forEach(num -> System.out.println(num));
●規則性を持つストリームの生成
IntStream/LongStreamインタフェースのrangeメソッドと同じようなことを他のストリームで行うには、iterateメソッドを使用する。
iterateメソッドの第1引数が規則の種となる値、第2引数の型はストリームの種類によって異なるが、Stringインタフェースの場合、java.util.function.UnaryOperatorインタフェースになる。
UnaryOperatorインタフェースは引数が1つのapplyメソッドを定義している。applyメソッドの引数と戻り値は同じ型とする。このapplyメソッドに規則性を 記述する。
applayメソッドの戻り値がストリームの要素になる。
iterateメソッドを使用すると、要素が無限に続くストリームになってしまいます。要素を有限にするために、要素数を指定するlimitメソッドを併用する。
次の例はiterateメソッドで0.0から始まり、1.0ずつ大きくなる数列を生成している。これだけだと無限に続いてしまうので、limitメソッドで要素数を10に抑えている。
DoubleStream.iterate(0.0, n -> n + 1.0) // 1.0ずつ値を大きくする
.limit(10) // 要素数を10までとする
.forEach(n -> System.out.println(n));
Streamインタフェースでもiterateメソッドを使用することができる。
次の例はaが増えていく文字列を要素とするストリームを生成する。
Stream.iterate(, s -> s + a) // aが1つずつ長くなる
.limit(5) // 要素数を5までとする
.forEach(System.out::println);
単純に何らかの法則性を持つ要素を連ねる場合はgenerateメソッドを使用する。Streamインタフェースの場合、generateメソッドの引数の型はjava.util.function.Supplierインタフェースとなる。
Supplierインタフェースが定義するgetメソッドは引数がない。このため、getメソッドだけで記述できる法則でストリームの要素を連ねていく。
次の例は20個の乱数が標準出力に出力する。
// 乱数を要素に持つストリームの生成
DoubleStream.generate(() -> Math.random())
.limit(20) // 要素数を20とする
.forEach(n -> System.out.println(n));