Java8の登場から、forループで色々処理していたものが、Streamという機能により表現出来るようになりました。
自分が、Javaを実務で触るようになった頃には既にJava8だったので、
Streamが無かったら、どんなに大変だっただろうと思うんですが。
Streamとは
ストリームとは、
配管みたいなもので、先に配管を作成して
終端メソッドのタイミングで、
実際にデータをその作った 配管に流していく。というイメージです。
ストリームの種類
ストリームには、汎用的なStreamクラスと
int / long / double に特化したIntStream/ LongStream / DoubleStream があります。
Stream
一番良く使うストリーム、
ジェネリクス型で、Objectを扱う為、すべてのクラスで使用できる。
Listなどのコレクション系のジェネリクスにはstreamメソッドが用意されているので、
そこからStreamを生成する事ができます。
// サンプル用のクラス
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
List<Person> list = new LinkedList<>();
list.add(new Person("hoge", 18));
list.add(new Person("piyo", 19));
list.add(new Person("foo", 20));
// コレクションから、ストリームの取得
Stream<Person> stream = list.stream();
IntStream / LongStream / DoubleStream
int / long / double がプリミティブ型なので、ストリームで扱うときは、
Integer / Long / Double で扱います。
そのまま、Streamで扱う事もできますが、
プリミティブ用に特化したメソッドが用意されています。
Streamを連結させていく中で、
Stream / IntStream / LongStream / DoubleStream のどれでデータが流れているのか理解しておく必要があります。
// サンプル用のIntStream
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
// プリミティブ型の配列を生成
int[] arr = intStream.toArray();
Stream<Integer>
であっても、プリミティブ型の配列を返したい場合は、
IntStream を通して取得する必要があります。
ストリームの変換
Stream から、IntStreamへ変換する
Stream::mapToInt
IntStream intStream = list.stream().mapToInt(o -> o.age);
Stream から、LongStreamへ変換する
Stream::mapToLong
Stream から、DoubleStreamへ変換する
Stream::mapToDouble
IntStream / LongStream / DoubleStream から、Streamへ変換する
IntStream::mapToObj
LongStream::mapToObj
DoubleStream::mapToObj
IntStream::boxed
LongStream::boxed
DoubleStream::boxed
IntStream intStream = list.stream().mapToInt(o -> o.age);
// IntStreamから、Stream<Integer>へ変換
Stream<Integer> integerStream = intStream.mapToObj(o -> o);
Stream<Integer> integerStream = intStream.boxed();
単純に、IntStreamから、Stream
IntStream::boxed を使った方が簡素に書けます。
Streamメソッド
Streamは、配管のようなものだと述べた通り。
終端メソッドを実行するまでは、実データは処理されていません。
中間メソッドを組み合わせたり、複数ストリームを結合して処理を記述する事が出来ます。
int totalAge = list.stream() // Stream<Person>を生成
.mapToInt(o -> o.age) // IntStreamに変換
.sum(); // 終端メソッドで合計値を取得
Person piyo = list.stream() // Stream<Person>を生成
.filter(o -> o.name.equals("piyo")) // piyoさんを検索
.findFirst() // 終端メソッド 最初にfilterでtrueを返した要素を取得
.orElse(null); // Optionalなので、なかった場合はnullを返す。
Person oldestPerson = list.stream() // Stream<Person>を生成
.max(Comparator.comparingInt(o -> o.age)) // 終端メソッド一番年齢高い人を検索
.orElse(null); // Optionalなので、なかった場合はnullを返す。
中間メソッド (Intermediate operation)
map
mapTo*
flatMap
peek
filter
distinct
sort
など。。
終端メソッド (Terminal operation)
reduce
collect
count
min
max
など。。
Streamは再利用できない
終端メソッドを実行すると、ストリームにデータが流れてきます。
一度使用した(データを流した)ストリームは再利用する事が出来ません。
IntStream stream = list.stream().mapToInt(o -> o.age);
int max = stream.max().orElse(0);
int min = stream.min().orElse(0); // ストリームを再利用するとエラー
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
一度使用したストリームは、閉じた状態になります。(closed)
閉じたストリームを利用する事は禁止されています。
なので、ストリームは毎回生成する必要があります。
おしまい
Streamという、考えかたはとても便利なもので、
例えば、RxJavaなどでは、データをストリームで流すことによって、
マルチスレッド処理が比較的簡単に書けます。
ただ、ストリームという概念を理解しておかないと、はっきり言って何やってるか分からん…になるので、
ちゃんと理解しておく事が大事でしょう。
C#には、Linqというものがありますが、
まぁ、似たような使い方が出来るものです。
(Linqは、クエリっぽく使うので、ちょっと目的が違うかも)