概要
GroovyはJavaなので、当然Java8から導入されたOptionalを利用できます。
Java8の新機能をあまり勉強していなかったので、今回OptionalをGroovyから使って勉強してみました。
結論から言うと、GroovyからJava8のOptionalとかStreamを使う旨味はあまりないかな、という感じです。
また、恐らくシンタックスの絡みだと思うのですが、Groovyからでは利用できない組み合わせなどもあるようです。
また、まだちゃんとJava8のOptionalを勉強したわけではないので、もっと効率的な書き方などあるとは思います。
もし何かあればコメントで教えていてだけると嬉しいです。
メモ:Optionalインスタンスは、要素が0個か1個しか無いサイズが固定されたコレクションだと考えると理解しやすい。
基本
// Optionalインスタンスを生成
Optional<String> hogeOpt = Optional.ofNullable("a")
// 値の取り出し方法
assert "aaa" == Optional.ofNullable("aaa").orElse("bbb")
assert "bbb" == Optional.ofNullable(null).orElse("bbb")
assert "bbb" == Optional.ofNullable(null).orElseGet({-> "bbb"})
// Optionalがnullじゃない場合にのみ処理をする
hogeOpt.ifPresent{hoge-> println hoge.length()}
//Optionalがnullじゃない場合に処理を実行して結果を返す(結果は自動的にOptional型にラップされる)
Optional<Integer> lengthOpt = hogeOpt.map{hoge -> hoge.length()}
println lengthOpt
// mapを使うと、Optionalに格納されている値が変化する。Optionalに格納されている値が、ある条件を満たしている場合にそのOptional自体がそのまま欲しい場合はfilterを利用する。
// 通常なら if(obj.length()>10)みたいな処理がifの条件に入るが、objがNullな場合がある。そのような場合にfilterを利用する。
// なお、条件に合致しない場合は戻り値はOptional.empty()になる
assert Optional.empty() == Optional.ofNullable(null).filter{it.length() > 2}
assert Optional.empty() == Optional.ofNullable("ab").filter{it.length() > 2}
assert Optional.empty() != Optional.ofNullable("abc").filter{it.length() > 2}
assert Optional.ofNullable("abc") == Optional.ofNullable("abc").filter{it.length() > 2} // mapと違い、戻り値は同じになることが解る。
// 具体的には以下のような使い方になるのでは?
// 成人かどうかを判断する。DBからユーザオブジェクトを取得、そのageプロパティはnullになる可能性がある。
def userObject = [age:null, "name":"koji"]
Optional<Integer> age = Optional.ofNullable(userObject.age)
if (age.filter({it >= 20}) != Optional.empty()) {
println "adult!"
} else {
println "child or null!"
}
// Scalaのcaseのように簡潔ではないけど、以下のようにemptyがそれ以外で処理を切り分けることも出来る。
switch(lengthOpt) {
case Optional.empty():
println "ng"
break
case Optional:
println "ok"
break
}
もっと具体的なサンプル
// 偶数だけ抽出して、さらにそれぞれの値を2倍して、その合計を求める。また、数字はStringとして格納されている。
// リストの扱いはGroovyのメソッドを利用した。
assert ["10", "-1", "2", "3", "-3", null, "-10", "2"].collect {
Optional.ofNullable(it) // Optionalに包む
}.collect { // 変換
it.map {it.toInteger()} // StringからIntegerに変換。nullがあるのにNullPointerExceptionが発生しない!
}.collect { // 変換と抽出
it.filter{ it % 2 == 0 && it > 0}
}.collect {
it.map{it*2}
}.collect{
it.orElse(0)
}.sum() == 28
// 同じ条件で、Java8のStreamを使ってみる
// ただし、Groovyからだとシンタックスの問題でreduceが使えないっぽい
assert ["10", "-1", "2", "3", "-3", null, "-10", "2"].stream().map {
Optional.ofNullable(it) // Optionalに包む
}.map { // 変換
it.map {it.toInteger()} // StringからIntegerに変換。nullがあるのにNullPointerExceptionが発生しない!
}.filter { // 変換と抽出。Optional.empty()なインスタンスも個時点で除外される
it.filter{ it % 2 == 0 && it > 0}.isPresent()
}.map {
it.map{it*2}
}.collect {
it.orElse(0)
}.sum() == 28
// 同じ条件を普段の自分がGroovyで書いているスタイルで。
["10", "-1", "2", "3", "-3", null, "-10", "2"].collect {
it?.toInteger()
}.findAll {
(it?:0) % 2 == 0 && it > 0
}.collect {
it * 2
}.sum()