jOOλ とは
jOOQ シリーズの一部として提供されている
Java の lambda や Stream まわりを使いやすく+強化してくれるライブラリ
jOOQ とは独立して単体で利用可能
ちなみに jOOλ の発音は公式によると
Product | Pronunciation | Sounds like |
---|---|---|
jOOλ | dʒuːəl | jewel (although some people also call it jOO-lambda) |
だそうです
jOOλ の概要
https://github.com/jOOQ/jOOL
https://www.baeldung.com/jool
jOOλ の構成
- org.jooq.lambda.function → Function を 1~16 用意
Java は Function, BiFunction だけ - org.jooq.lambda.tuple → Tuple1~16 を用意
Java には Tuple がない(原則としてはクラス定義しろ)のに対して、Tuple があると複数の任意の型のオブジェクトを保持できる型を定義することができる。Stream の途中や結果等で一時的にしか使わないデータ格納用クラスとして重宝 - org.jooq.lambda.Seq → 他の関数型言語を参考にして Java の Stream を強化して便利にした!
- org.jooq.lambda.Unchecked → Stream 中のラムダでの例外処理記述をワンライナーで書けるよ!
あらためて Stream とは?
繰り返し処理をパイプライン処理として抽象化したもの
具体的にはデータ集合を Stream として、Stream を媒介としたメソッドチェインをパイプラインとして一連の処理を行うもの
for 文ではデータ集合→一つを変数に取り出しそれに関してループの形で逐次処理として記述していたものを
- Stream という形でデータ集合を抽象化
- 用意されているメソッドで処理
- デフォルト処理がない or 変更したい処理(中間・終端)の具体的な内容は利用者が lambda 等で記述
処理としては3段階がある
生成 | 中間 | 終端 |
---|---|---|
Stream データの生成 | フィルタ・分類・整形・変換等 | 集計・計算等 |
Stream.of Collection系の .stream()等 |
filter map sorted等 |
forEach collect reduce等 |
生成・終端は1回だけ
中間処理は複数回可能
https://speedment.com/become-a-master-of-java-streams-part-1-creating-streams/ から引用
Stream の大原則(Stream の javadoc から)
- 非干渉でなければいけない(ストリームのソースを変更しない)
- ほとんどの場合、ステートレスでなければいけない(その結果は、ストリーム・パイプラインの実行中に変化する可能性のあるどの状態にも依存すべきでない)
補足的にある種の制限
- Stream の要素は全て一つの同じ型
- 原則的にはメソッドチェイン間やメソッドチェイン内での状態引き継ぎを独自に記述できない(各処理メソッドは Stream の処理加工の input/output のみを引き受ける)
そういうことがやりたければ、素直に for に頼った方がいい
後は、ループでは書けるけど、Stream だと複数回に分かれる場合などで、特にパフォーマンスを気にする場合は for の方がいい場合などもある
とはいえ、逆の見方をすれば
- for 文内で if で分岐したり cotinue,break の利用
- 多重な for ループ
といった可読性の低さや処理の煩雑さのデメリットより
Stream の簡潔で目的の明確な処理の組み合わせの方を取ることが良い場合も多いともいえて
次のように Stream 処理をより簡潔で強力に書ける jOOλ の導入でよりそのメリットが増すかと
Stream についてより詳しくは「Stream API がもっとわかる記事」などを参照
jOOλ の使いどころ
Seq.java や SeqImpl.java をみると分かりますが、
ほとんどが標準的な Stream+Collector をラッピングした便利メソッドの集合体
では、Stream 使わずに jOOλ を使うメリットは?
提供されたメソッドで圧倒的に簡単に安全に使えること
- 生成処理が基本 Seq.seq(~) や Seq.of(~) に与えてあげれば大抵のデータ集合が Stream 化できる
- ソースみれば分かりますが ↑ の生成処理には null チェックが含まれていて null 安全(Stream.empty として扱われる)
- 中間処理の ~Join では2つの Stream の結合処理ができて便利(Stream の原則からはちょっと外れるかもだけど…)。C# の LINQ とかみたいな、インメモリ SQL 的に扱える。
- 終端処理、特に Collector まわりが楽。例えば、最後にtoList(), toMap() つけるだけなのは楽
- IDE の補完機能を考慮すると、記述の際に Seq.seq(~) と書き始めれば、後は . で新しいメソッドを補完していく形で記述が続けられるので、Stream を書くためのあれこれな寄せ集めをしなくて良い(自分が書きたいロジックに集中しやすい)
例えば、
- dataList というリストから
- Amount が 1000 以上のものを抽出して
- masterList と突合して
- 必要な情報を集めてオブジェクトを作って
- List にする
みたいなことを
Seq.seq(dataList)
.filter(x -> x.getAmount() >= 1000)
.innerJoin(masterList, (data, master) -> data.getMasterId() == master.getId())
.map(t -> DataResource.of(t.v1.getId(), t.v1.getName(), t.v2.getTypeName(), t.v1.getAmount()))
.tolist();
と書ける
基本、Stream や Collector のラッパーなので、
使い方に迷ったら jOOλ ではなく Stream で調べると良い
(jOOλ 独自なのはしょうがないけど…そっちもまぁ解説あるし、jOOλ のソースを読むと色々と参考になる)
備考
ちなみに jOOλ は parallel には対応してない
all Seq's are sequential and ordered streams, so don't bother to call parallel() on it, it will return the same Seq
とあるように
default Seq<T> parallel() {
return this;
}
という実装になっているので
parallel したい場合には素の Stream を使うこと
蛇足
これは jOOλ 固有じゃないけど結構はまり勝ちなのが reduce()
reduce は畳み込み処理だけど、再帰として考えると分かりやすいかも!?
さらに3項 reduce を使えば要素の型と異なる型の戻り値をかえせて重宝する
参考 → Java8 StreamAPI reduce() で入力要素とは異なる型にして返したい