Stream API
関数型プログラミングについて
本題に入る前に、関数型プログラミングについて触れておきたいと思います。関数型プログラミングとは、「計算を関数の連鎖で行うコーディングスタイル」のことです。
- Haskell
- Scala
- etc…
などのプログラミング言語がそれにあたります。
- Java
- Python
- JavaScript
といったよく知られた言語は、関数型プログラミングの概念をサポートしていますが、「関数を実行する言語」です。
関数型プログラミングは「式(関数)を評価する」と表現します。
Stream APIとは?
Stream APIとは、関数型プログラミングのアプローチを取り込んだライブラリのことで、クラス群またはインターフェース群のことです。パッケージのクラス群がそれにあたります。代表的なものに、Streamインターフェースがあります。
Streamインターフェース
Streamとは、「順次および並列の集約操作をサポートする要素のシーケンス」とJavaのリファレンスに記載されています。
以下は、リファレンスの抜粋です。
public interfaceStream<T> extendsBaseStream<T,Stream<T>> 順次および並列の集約操作をサポートする要素のシーケンスです。次の例は、とを使用する集計操作を示したものです。
シーケンスとは、順番に並んだデータの集合を指します。
- 配列
- List
- Stream
Streamには、配列やListにはない、順次および並列の集約操作がサポートされています。
順次の集約操作とは?「要素を1つずつ順番に評価してその結果を集約すること」です。、、などのメソッドの事です。
並列の集約操作とは?「1つずつではなく、各要素を同時に並行して処理をする」という事です。parallelStreamというメソッドを使うと、それ以降の集約操作が並列になります。
Stream APIのラムダ式
ラムダ式とは、「匿名クラスで実装した関数型インターフェースの記述を省略したもの」でした。
ラムダ式に変換する10の手順を、逆の手順で匿名クラスに戻したいと思います。以下の例を使って、ラムダ式の意味を紐解いていきましょう!
この例は、1から5の数値の羅列をリストに変換して、変数numsに代入しています。numsからstreamを取得して数珠繋ぎ風にメソッドを呼び出しています。このような繋ぎ方(呼び出し方)をメソッドチェーンと言います。これが川のようにワンライナー(1行)で流れていくのでStreamなわけです。
filter
filterメソッドのリファレンスを見てみましょう。
Stream<T> filter(Predicate<? superT> predicate) このストリームの要素のうち、指定された述語に一致するものから構成されるストリームを返します。 これは中間操作です。 パラメータ: - 各要素を含めるべきか判定する目的で各要素に適用する、非干渉でステートレスな述語戻り値:新しいストリーム
引数の型は、プレディケートになっています。これは関数型インターフェースです。
インタフェースPredicate<T> 型パラメータ: - 述語の入力の型 関数型インタフェース:これは関数型インタフェースなので、ラムダ式またはメソッド参照の代入先として使用できます。
抽象メソッドは、testというメソッドになります。
test指定された引数でこの述語を評価します。パラメータ:- 入力引数戻り値:入力引数が述語に一致する場合は、それ以外の場合は
testメソッドには、引数tを評価する処理をオーバーライドしてください、という意味になるわけです。戻り値は評価した結果を返す、boolean型になっています。
ラムダ式を、匿名クラスにリバースしていきましょう。
まず、ラムダ式を消します。
Predicateインターフェースをnewして匿名クラスを宣言していきます。
変数numsの要素、1、2、3、4、5は数値なのでジェネリクスの部分はラッパークラスのInteger型にしてやります。匿名クラスの中身はまだ何もありません。
testメソッドをオーバーライドします。
引数のデータ型は、引数名はとしました。ここに1、2、3、4、5が順番に入ってくるというわけです。
testメソッドの中身を実装します。
引数を2で割って余が0かを評価しています。偶数なら、奇数ならをリターンします。匿名クラスの完成です!先ほどのラムダ式はこういう意味だったのです。
forEach
forEachのリファレンスです。
forEach
引数の型はConsumerインターフェース型です。
インタフェースConsumer<T> 型パラメータ: - オペレーションの入力の型 既知のすべてのサブインタフェース:Stream.Builder<T> 関数型インタフェース:これは関数型インタフェースなので、ラムダ式またはメソッド参照の代入先として使用できます。
関数型インターフェースなのでラムダ式に省略できます。
accept指定された引数でこのオペレーションを実行します。パラメータ: - 入力引数
acceptという1つの抽象メソッドを持っています。
ラムダ式を、匿名クラスにリバースしていきましょう。
forEachメソッドのラムダ式を消去します。
Consumerインターフェースをnewして匿名クラスを宣言していきます。
匿名クラスの中身を書いていきましょう。
acceptメソッドを、i * 2してprintlnする処理でオーバーライドしました。acceptメソッドの戻り値はvoidなのでreturn文はありません。匿名クラスが完成しました。
この例では、偶数2と4の倍、4と8が出力されます。
Stream APIで評価されるラムダ式の省略表記を、匿名クラスに戻していくと、実装内容の意味がわかると思います!