26
11

More than 5 years have passed since last update.

KotlinのtakeIf、takeUnlessを使う

Last updated at Posted at 2019-03-29

元本 : "Using Kotlin takeIf(or takeUnless)"
takeIftakeUnlessのことを調べていたら、上記に良い記事があったので、自分なりに頑張って翻訳してみました。

Kotlinの標準関数には、If文のようなtakeIftakeUnlessという2つの関数があります。

次の例文のようにIf文の代わりに使う場合があります(オススメしません)

// Original Code
if (status) { doThis() }

// Modified Code
takeIf { status }?.apply { doThis() }

takeIfとtakeUnlessについて

例文のtakeIftakeUnlessをどうやって使うかの前に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を返しても呼び出されるため、statustruefalseと関係なくdoThis()関数が実行されます(この例のdoThis()someObjectの関数ではありません。)
takeIf関数の後ろに存在する演算子"?."を付けることによって結果が大きく異なるため、論理的にはとても重要です。

26
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
11