Kotlinのスコープ関数について公式ドキュメントを参考に特徴とユースケースをまとめてみます。公式のドキュメントがとてもわかりやすくまとまっているので、詳細はそちらを参考にしてください。Kotlinのversionは1.3.21です。
let
public inline fun <T, R> T.let(block: (T) -> R): R
- レシーバオブジェクトをラムダの引数(it)として参照する。
- ラムダの結果を返す
ユースケース
nullチェック
// letを使わない場合 val number: Int? = 1 if(number != null) println(number) // letを使う場合 val number: Int? = 1 number?.let { println(it) }
メソッドチェーンの結果に対して関数を使いたい場合
// letを使わない場合 val list = listOf("one", "two", "three") val result = list.map { it.toUpperCase() }.filter { it != "three" } println(result) // letを使う場合 val list = listOf("one", "two", "three") list.map { it.toUpperCase() }.filter { it != "three" }.let { println(it) }
メソッドチェーンの途中結果を読みやすい名前に変換したい場合
// letを使わない場合 val list = listOf("alice", "bob", "chris") val person = list.first() println(person.toUpperCase()) // letを使う場合 val list = listOf("alice", "bob", "chris") val result = list.first().let { person -> println(person.toUpperCase()) }
apply
public inline fun <T> T.apply(block: T.() -> Unit): T
- レシーバオブジェクトをthisとして参照する
- レシーバオブジェクトを返す
ユースケース
オブジェクトの設定をする場合
// applyを使わない場合 val datasource = SampleDataSource() datasource.url = "" datasource.username = "" datasource.password = "" // applyを使う場合 val datasource = SampleDataSource().apply{ // オブジェクトの設定 url = "" username = "" password = "" }
with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
- レシーバーオブジェクトthisとして参照する
- ラムダの結果を返す
- 拡張関数としてではなく引数としてレシーバーオブジェクトを受け取る
ユースケース
プロパティやメソッドへのアクセスを簡潔にしたい場合
// withを使わない場合 println(datasource.url) println(datasource.username) println(datasource.password) // withを使う場合 with(datasource){ println(url) println(username) println(password) }
run
public inline fun <R> run(block: () -> R): R public inline fun <T, R> T.run(block: T.() -> R): R
- オブジェクトをレシーバーオブジェクトthisとして参照する
- ラムダの結果を返す
ユースケース
オブジェクトの初期化と、オブジェクトを使った計算を返す処理をまとめて行いたい場合
// runを使わない場合 val datasource = DataSource() datasource.url = "" datasource.username = "" datasource.password = "" val result = datasource.fetch() // runを使う場合 val result = DataSource().run{ // オブジェクトの設定 url = "" username = "" password = "" // オブジェクトの処理を実行 fetch() }
ローカル変数と処理を1つにまとめて名前をつけたい場合
// runを使わない場合 val schema = "https://" val hostname = "localhost" val port = ":8080" val path = "/" val url = schema + hostname + port + path // runを使った場合 val url = run{ val schema = "https://" val hostname = "localhost" val port = ":8080" val path = "/" schema + hostname + port + path }
also
public inline fun <T> T.also(block: (T) -> Unit): T
- レシーバオブジェクトをラムダの引数(it)として参照する
- レシーバオブジェクトを返す
ユースケース
オブジェクトのプロパティや関数ではなく、オブジェクトへの参照を必要とする操作を行いたい場合
// alsoを使わない場合 val list = mutableListOf(1, 2, 3) println(list) list.add(4) println(list) // alsoを使った場合 val list = mutableListOf(1, 2, 3) list.also { println(it) } .add(4) .also { println(it) }
外部スコープからのThis参照を暗黙的にしたくない場合
// applyを使う場合 val datasource = SampleDataSource().apply{ // オブジェクトの設定 url = "" username = "" password = "" } // alsoを使う場合 val datasource = SampleDataSource().also{ // オブジェクトの設定 it.url = "" it.username = "" it.password = "" }
最後に
スコープ関数のユースケースについて公式ドキュメントを参考にまとめてみました。 公式ドキュメントにも書いてある通り、スコープ関数をネストさせたり、むやみにスコープ関数をメソッドチェーンしすぎると、itやthisが乱立してしまい、読みづらいコードになってしまうので注意が必要です。
参考資料
https://kotlinlang.org/docs/reference/scope-functions.html#scope-functions