元本 : "Using Kotlin takeIf(or takeUnless)"
takeIf
とtakeUnless
のことを調べていたら、上記に良い記事があったので、自分なりに頑張って翻訳してみました。
Kotlinの標準関数には、If文のようなtakeIf
とtakeUnless
という2つの関数があります。
次の例文のようにIf文の代わりに使う場合があります(オススメしません)
// Original Code
if (status) { doThis() }
// Modified Code
takeIf { status }?.apply { doThis() }
takeIfとtakeUnlessについて
例文のtakeIf
とtakeUnless
をどうやって使うかの前にtakeIf
の関数の定義をみてみましょう。
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?
= if (predicate(this)) this else null
上記のtakeIfを見ると、次のような特徴があるのがわかります。
・ この関数はTオブジェクト自身から呼び出される。つまり、T.takeIf
で使える
・ takeIfの条件関数のpredicate
はパラメーターとしてT
オブジェクトを受け取る
・ takeIf
の条件関数のpredicate
の結果によってT
オブジェクトのthisやnullを返す
takeUnless
関数はtakeIf
関数とは逆に動作します。 takeUnless
は条件関数がtrue
の場合null
を返し、false
の場合thisを返します。
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T?
= if (!predicate(this)) this else null
使い方
上記の特徴を元に下記のようなif文コードに相応するtakeIf
関数の使い方がわかります。
1. この関数はTオブジェクト自身から呼び出される。つまり、T.takeIfで使える。
nullをチェックし、結果によって作業を実行する利点があります。
// Original code
if (someObject != null && status) {
doThis()
}
// Improved code
someObject?.takeIf{ status }?.apply{ doThis() }
2. takeIfの条件関数 predicate はパラメーターとして T オブジェクトを受け取る
predicate
関数のパラメーターとしてTオブジェクトを使う場合、もっと単純にコードの作成ができます。
// Original code
if (someObject != null && someObject.status) {
doThis()
}
// Better code
if (someObject?.status == true) {
doThis()
}
// Improved code
someObject?.takeIf{ it.status }?.apply{ doThis() }
上記の改良されたコードのように作成もできますが、オリジナルコードでは使っていなかった追加キーワードのtrue
との比較が必要です。ですが、これは最適なコードではありません。
3. takeIfの条件関数 predicate の結果によって T オブジェクトのthisやnullを返す
takeIfの条件関数のpredicate
の結果がtureの場合thisを返すので、これを利用して演算を繋いで処理するチェーンリング技法が使えます。
従って、下記のようなコードに改良できます。
// Original code
if (someObject != null && someObject.status) {
someObject.doThis()
}
// Improved code
someObject?.takeIf{ status }?.doThis()
またはKotlin Docの例のようにtakeIf
関数が条件に満たされない場合、null
を返す特性を利用してあるデータを返したりエルビス演算子(?:)を使って関数を終了させる時にも使えます。
// takeIfの条件が満たされなかったらエラーを返す
val index
= input.indexOf(keyword).takeIf { it >= 0 } ?: error("Error")
// takeIfの条件が満たされなかったらreturnし、終了する
val outFile
= File(outputDir.path).takeIf { it.exists() } ?: return false
注意点
次のようなコードの場合は注意が必要です。
// 構文的には正しいです。ですが、論理的には間違っています。
someObject?.takeIf{ status }.apply{ doThis() }
// 正しい例文です。(applyの前の演算子 "?." に注意が必要です)
someObject?.takeIf{ status }?.apply{ doThis() }
一列目の例文ではapply
関数はtakeIf
関数がnull
を返しても呼び出されるため、status
のtrue
、false
と関係なくdoThis()
関数が実行されます(この例のdoThis()
はsomeObject
の関数ではありません。)
takeIf
関数の後ろに存在する演算子"?."を付けることによって結果が大きく異なるため、論理的にはとても重要です。