いまさらですが、JavaのStreamについてざっくりまとめます。
基本的にはStreamのJavaDocを参考にしながらまとめてますが、メソッド内引数の型変数など細かいところは省略しているので、詳しくはJavaDocの方を参照。
StreamはJava8から登場した概念で、要素の集まりに対して様々な処理を行うことができるものです。
Streamの操作
Streamの操作は生成、中間操作、終端操作の3段階に分けられます。
生成と終端操作は1つのストリーム操作につき一度だけ実行しますが、中間操作は複数回行うことができます。
中間操作のメソッドはStreamインスタンスを返すメソッドとして設計さているため、メソッドチェーンによる記述が可能です。
結果変数 = 生成().中間操作1().中間操作2().中間操作3().終端操作();
生成
生成はその名の通り、Streamを最初に生成するための処理です。
しかし、Streamはインターフェースであり、実装クラスも普通にインスタンス化される形では提供されていません。
Collectionではstream()
やparallel()
メソッドが提供されており、要素のStreamを取得することができますが、Collectionを生成せずにStreamだけ得たいという場合も多いです。
そこで、Streamに用意されている以下のようなstaticメソッドを使用してStreamを生成することができます。
メソッド | 内容 | 要素数 |
---|---|---|
empty() | [] | 有限(空) |
of(Object e1, Object e2, Object e3, …) | [e1, e2, e3, …] | 有限(引数と同じ) |
iterate(T s,Function f) | [s, f(s), f(f(s)), f(f(f(s))))…} | 無限 |
generate(Function f) | [f(),f(),f(),f()…] | 無限 |
concat(Stream s1, Stream s2) | [s1の全要素, s2の全要素] | 引数による |
T:Stream内要素の型 |
また、Stream.builder()
で得られるBuilderを使用し、Streamを生成する方法もあります。
Builder<Integer> builder = Stream.builder();
IntStream s = Stream.builder().add(1).add(2).add(3).build();
iterate
やgenerate
で生成される無限のStreamは後述する中間操作limit
を使って、処理を中断させる必要があります(そうしないと無限ループになります)。
中間操作
中間操作はStream内の要素に対して操作を行います。
中間操作では操作を各要素に適用後の新しいStreamインスタンスが返されるため、メソッドチェーンで中間操作を連結していくことができます。
中間操作には大きく分けて特性変更操作、ステートレス操作、ステートフル操作があります。
特性変更操作
特性変更操作はストリーム自体の性質を変更する操作です。
中身の要素は変化しません。
Streamではなく、その親インターフェースであるBaseStreamで定義されています。
メソッド | 内容 |
---|---|
parallel() | 並列ストリーム |
sequential() | 順次ストリーム |
unordered() | 非順序付けストリーム |
onClose(Runnable r) | close時のハンドラの登録 |
ステートレスな中間操作
ステートレスな中間操作とは、1つの要素に操作を行う場合に他の要素に影響を受けない中間操作のことです。
そのため、並列処理に向いており、中間処理はできるだけステートレスにすることが推奨されています。
以下、eやen(n:整数)をストリーム内の要素とします。
メソッド | 内容 |
---|---|
filter(Predicate p) | p(e)がfalseの場合、要素eを削除 |
map(Function f) | [f(e1),f(e2),f(e3),…] |
flatMap(Function f) | [f(e1)が返すストリームの全要素, f(e2)が返すストリームの全要素,f(e3)が返すストリームの全要素,…] |
peek(Function f) | ストリームの要素はそのままで、各要素に対してfを実行する |
ステートフルな中間操作
ステートフルな中間操作とは、1つの要素に操作を行う場合に他の要素の影響を受ける中間操作のことです。
操作によっては結果を返すためにStream内のすべての要素が必要になる場合もあります。
例えば並べ替え処理を実現するsorted
メソッドなどがその例です。
他の要素の影響を受けることから、ステートフルな中間操作では並列処理の際に複雑な処理が制御が必要となり、順次処理よりも効率が低下する可能性があります。
メソッド | 内容 |
---|---|
distinct() | ストリーム内の重複要素を削除 |
sorted() | Comparableの実装クラスに対する、自然順序でのソート |
sorted(Comparator c) | Comparatorを使用したソート |
limit(long maxSize) | maxSize個以内の要素の長さに切り詰める |
skip(long n) | 最初のn個の要素を破棄する |
limit
は特に短絡中間操作と呼ばれます。
短絡操作とは、無限ストリームを有限ストリームにできる可能性がある操作のことです。
limit
やskip
は、distinct
やsorted
に比べて要素の数だけを見るために並列処理が高速だと思われがちですが、順序付けられたストリームでは「最初からn個」を調べる必要があるためコストが高い操作になります。
並列実行を優先したい場合かつ順序の維持が不要であれば、unordered
によって順序付けをしないようにすることで効率の向上が期待できます。
終端操作
終端操作は呼び出された時点でのStreamの内容に対して処理を行い、Stream以外の結果(結果なし(void)を含む)を返します。
終端操作完了後はそれまで使用してきたStreamは使用不可能になり、再度使おうとするとIllegalStateExceptionがthrowされます。
そのため、Streamオブジェクトは変数に取るべきではなくメソッドチェーンで処理するのが正しいと言えます。
メソッド | 戻り値型 | 内容 |
---|---|---|
close() | void | ストリームを閉じる。onCloseで登録したすべてのクローズハンドラが呼び出される |
iterator() | Iterator | イテレータを返す |
spliterator() | Spliterator | スプリッテレータを返す |
forEach(Consumer action) | void | 各要素に対してactionを実行する。順序を保証しない |
forEachOrdered(Consumer action) | void | forEachと同じだが、順序を保証する |
toArray() | Object[] | 配列化する |
reduce(T unit, BinaryOperator acc) | T | リダクション(畳み込み)。単位元unitに対して要素を累積関数accで結合していった結果を返す。 |
collect(Supplier factory, BiConsumer acc, BiConsumer combiner) | 結果コンテナ | 可変リダクション。factoryで生成した可変コンテナ(例えばArrayList)に対し、accで要素を追加し、combinerで各コンテナを結合する |
min() | Optional | 最小要素を返す。リダクションの特殊な場合 |
max() | Optional | 最大要素を返す。リダクションの特殊な場合 |
count() | long | 要素数を返す。リダクションの特殊な場合 |
anyMatch(Predicate p) | boolean | p(e)の評価結果が1つでもtrueになる場合にtrueを返す |
allMatch(Predicate p) | boolean | p(e)の評価結果がすべてtrueになる場合にtrueを返す |
noneMatch(Predicate p) | boolean | p(e)の評価結果がすべてfalseになる場合にtrueを返す |
findFirst() | Optional | 最初の要素を返す。順番を保持しないストリームでは任意の要素を返す |
findAny() | Optional | 任意の要素を返す |
T:Stream内要素の型 |
分かりづらいのはreduce
やcollect
といったリダクション操作あたりでしょうか。
これについては近いうちに別記事で詳しく書きたいと思います。→書きました。
また、スプリッテレータという耳慣れない名前も出てきました。
スプリッテレータは並列処理可能な(分割可能な)イテレータです。
ストリームの中ではスプリッテレータを取り扱っており、並列処理時には分割された各スプリッテレータに対して処理を行っています。