Kotlin言語における標準の高階関数apply、also、let、runは、オブジェクトのチェーン可能な構成やローカルな変更を最小限のボイラープレートで簡素化するために導入されました。これらの関数は、変更可能および不変のオブジェクトを扱うのを容易にし、変換のチェーンを簡潔に表現することを可能にし、変数のスコープに関する問題を軽減します。
これらの関数は、ビルダーのパターンとフルーエントインターフェースのアプローチから取り入れられました。クリーンなコードを求め、余分な補助変数の宣言を排除する努力の結果です。
従来のアプローチでは、オブジェクトに何度もアクセスするか、一時変数を使用する必要があり、読みやすさが低下し、エラーのリスクが増加します:
val user = User() user.name = "Alex" user.age = 26 user.isActive = true
apply、also、let、run関数を使用することで、表現力が向上します:
val user = User().apply { name = "Alex" age = 26 isActive = true }
簡単な説明:
data class User(var name: String = "", var age: Int = 0, var isActive: Boolean = false) val configuredUser = User().apply { name = "Alice" age = 30 isActive = true } debugUser(configuredUser.also { println("User is configured: $it") }) val emailLength = configuredUser.email?.let { it.length } ?: 0 val description = configuredUser.run { "$name ($age)" }
主な特徴:
let関数は、操作対象のオブジェクトを変更できますか?
let関数はオブジェクトを変更するためには設計されていません。むしろ、値に変換を適用し、結果を返すために使用されます。letの内部ではthisの代わりにitが利用可能です。
val upperName = user.name.let { it.uppercase() } // letはuserを変更しません
applyとrunの違いは何ですか?
どちらもラムダ内でthisのコンテキストで動作しますが、返される値が異なります:
// apply val building = StringBuilder().apply { append("start-") append("end") } // buildingはStringBuilderです // run val result = StringBuilder().run { append("start-") append("end") toString() } // resultはStringです
applyとletを入れ子にして使用できますか?可能な場合、それはいつ正当ですか?
はい、入れ子はオブジェクトの集約や段階的な設定に推奨されています。特にnullableを扱う場合に有効です:
val userInfo = user?.apply { isActive = true }?.let { "${it.name} is active: ${it.isActive}" }
コード全体でletがオブジェクトの変更に使用され、applyとletが混在。その結果、期待されている箇所でオブジェクトが変更されず、値がチェーンの中で失われる。
利点:
欠点:
applyとalsoを設定やロギングに使用し、letはnullableを扱うためと結果を取得するためのみに使用し、runは新しい値を計算する必要がある変換のチェーンに使用。
利点:
欠点: