Java8 Stream APIの基本(4) - ストリームの内部
先日の記事ではStream
の基本的な生成処理について述べた。
今回はStream
の内部処理で扱われるSpliterator
について解説する。
Spliterator
Spliterator
は分割可能なイテレータであり、Stream
の低レベルAPI用のインターフェースである(名前の由来はsplitとiteretorの合成と思われる)。コレクションの繰り返しにはIterator
が使われるように、Stream
の要素の走査は内部的にSpliterator
を用いている。
Spliterator
には 要素を2つに分割する trySplit
メソッドがあり、並列ストリームで各スレッドに要素を分割する方法として使用されている。
また中間・終端走査や、並列処理を効率化するヒントとして、characteristics
(性質)、 estimateSize
(サイズ)等の属性を持つ(characteristics
については後述する)。
Spliterator
を作成することで、以下のような任意のデータ構造をストリームにすることができる。
- 2つの
List
など、複数のデータから1要素ずつ要素を取得し、1つにまとめるストリーム(zip
メソッドの実現。次回解説) - 標準入力などから1文字ずつ読み取るストリーム
- JDBCの
ResultSet
から1レコードずつフェッチするストリーム(遅延ローディングの実現)
Spliterator
を作成するには、Spliterator
を実装したクラスを作成する方法と、Iterator
を実装したクラスから構築する方法の2通りがある。
後者の方が容易なため、この記事では後者の方法について解説する。
Iterator
からSpliterator
の生成
Spliterators
クラスは、Spliterator
に関するユーティリティクラスであり、Iterator
やコレクション等からSpliterator
を生成する方法を提供する。
Iterator
からSpliterator
を生成するメソッドは以下の2つがある。
spliterator(Iterator<? extends T> iterator, long size, int characteristics)
spliteratorUnknownSize(Iterator<? extends T> iterator, int characteristics)
(性質SIZEDの指定は無効)
両者の違いはサイズを指定するかどうかであり、予め要素の個数がわかるなら前者、Reader
の読み込みなどサイズが不明な場合は後者を用いる。
現在のStream
の実装では、サイズが有限である場合に並列処理が最適化されるようになっている。そのため、サイズが予めわかる場合は前者を用いた方が良い。
(当方で検証した結果、無限ストリームの場合、要素がおおよそ1000以下の場合では並列ストリームであっても単一スレッドで処理が行われた。一方、有限ストリームの場合は要素数に関係なく複数スレッドで並列処理が実行された。)
Spliterator
から、Stream
を生成するには、StreamSupport#stream
を用いる。
内部的には上記のストリーム生成処理はほぼ全て、これらのクラスを用いている。
characteristics
(性質)
characteristics
はストリームを生成したデータがどのような性質を持っているかを示す。
元のコレクションにおいては、List
には順序があり、 Set
には要素の重複が無い、などの性質があるが、Stream
に変換された時点で生成前のコレクションの型の情報は消えてしまう。このため、生成前のコレクションの性質を保持するためのにcharacteristics
という属性を持たせている。
(実際上記で紹介した各Stream
の生成メソッドは、生成する型に応じた性質を持ったSpliterator
を内部で生成している)。
characteristics
は、Spliterator
に定数として定義されたint値だが、2進数のフラグとして複数の性質を持てるようになっている。
代表的な性質を以下に示す。
性質 | 意味 | 主な例 |
---|---|---|
SIZED |
サイズが有限である | List , Set , Array , Stream#of |
ORDERED |
順序が決まっている | List ,Array , Stream#iterate , Stream#generate |
SORTED |
要素がソート済み | TreeSet |
DISTINCT |
要素が全て一意 | Set |
NONNULL |
nullの要素がない |
性質の値によって、Stream
は処理の効率化を行う場合がある。
例えば、以下のようにList
に無理やり本来とは異なる性質を与えると、本来行われるべき処理が行われないことがわかる。
(検証のために行っているので、実際に実装してはならない。)
// ソート済みでもユニークでもないリスト List<Integer> list = Arrays.asList(1,2,2,5,4,3); // 重複除去、ソートが行われ、 12345 が結果となる。 list.stream().sorted().distinct().forEach(System.out::print); System.out.println(); // ソート済み、ユニークの性質を与えたStreamを生成する。 Stream<Integer> st = StreamSupport.stream( Spliterators.spliterator(list.iterator(), list.size(), Spliterator.SORTED | Spliterator.DISTINCT) , false); // 入力値のまま、122543 と表示される。 st.sorted().distinct().forEach(System.out::print);
まとめ
先日の記事に続き、ストリーム内部で使われているSpliterator
を解説した。
次回は、Spliterator
を用いた独自のStream
作成例について解説する。
[前多 賢太郎]