Java Streamの種類

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は、クエリっぽく使うので、ちょっと目的が違うかも)

コメントを残す