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で評価されるラムダ式の省略表記を、匿名クラスに戻していくと、実装内容の意味がわかると思います!