Kotlinのスコープ関数(let, run, apply, also)を整理してみた
はじめに
普段、業務でWebシステムのサーバサイドをKotlinで開発しています。
Kotlinには let や apply といった「スコープ関数」と呼ばれる便利な機能があります。一方で、実務の中では let を使う場面が特に多く、他のスコープ関数は用途が限定されている印象です。
そこで今回は、自分自身の備忘録も兼ねて、これらのスコープ関数の違いと使い分けを整理してみました。
スコープ関数の違いを整理する
スコープ関数は5種類(let, run, with, apply, also)ほどありますが、大きく違うのは以下の二つです。
- オブジェクトの参照方法: ブロック内で「it」として扱うか、「this」として扱うか
- 戻り値: ブロックの「最後の行の結果」を返すか、「オブジェクト自身」を返すか
これらを表にまとめると以下のようになります。
| 関数 | オブジェクト参照 | 戻り値 | 主な用途のイメージ |
|---|---|---|---|
| let | it |
結果 | 一時変数の変換、?.let {} と組み合わせた使用 |
| run | this |
結果 | 複数のプロパティを使った計算 |
| apply | this |
自分自身 | オブジェクトの初期化・設定 |
| also | it |
自分自身 | ログ出力などの副作用(Chain) |
それぞれの使い方(基本的なパターン)
1. let
一番よく使う関数です。letは「ある値を別の値に変換する(T -> R)」処理を、スコープを区切って書くときによく使います。
実務では、Nullable な値に対して ?.let { ... } と組み合わせて、
- 左辺が
nullなら何もしない -
nullでなければ、letの中で non-null な値として扱う
というようなパターンで使用しています(null かどうかを判定しているのは ?. の safe call で、let は「non-null」になった値を受け取って処理する役割)。
// emailがnullでなければ処理を実行する
request.email?.let {
userService.findByEmail(it)
}
2. apply
オブジェクト生成とその後の設定(プロパティへの代入)をまとめて書くことができます。this は省略可能なので、記述がスッキリします。
// インスタンス生成と設定をまとめて、そのままreturnできる
return User().apply {
this.name = "太郎" // thisは省略可
this.age = 25
}
3. also
戻り値が「自分自身」なので、処理の流れを変えずに「ついでに何かする」ときに使えます。
// DB保存処理の後に、保存した内容をログに出す
return userRepository.save(user).also {
logger.info("ユーザを登録しました: ${it.id}")
}
4. run
apply と似ていますが、こちらは「結果」を返します。オブジェクトのプロパティを複数使って計算し、値を返す場合などに適しています。
// this.age, this.serviceYears と書かずに直接プロパティにアクセスできる
val bonus = user.run {
age * 10 + serviceYears * 100
}
まとめ・所感
今回、改めてスコープ関数を整理してみて、それぞれの違い(it か this か、戻り値は何か)が整理できました
ただ、自分の業務コードに当てはめて考えてみると、「意外と let 以外は登場頻度が低いかも?」というのが正直な感想です。
特に apply については、最初は「オブジェクトの構築に使えそう」と思いましたが、 data class を使うことが多く、コンストラクタで値を渡す形の方がイミュータブルで扱いやすい場面が多いと感じました。
Discussion
実用的な例がまとまってて良い記事だと思いました!
letの利用用途をNull checkと書いてるんですが、Null checkできているのは、Nullableに対する?.の部分でsafe callできてるからかなと思ったので、「letの用途」という意味ではちょっとニュアンスが別で取られてしまうかな?と思いましたmmlet自体はT -> R への(変換)処理をするscope関数だと思うので、サンプルで記載している処理も、
emailがnon nullを担保して(safe call)、Userへの処理をしている(letの中の処理)と思うので、letが担っている実体としては、email -> userの処理をscopeで実現しているという説明の方が合うかなと思いました🙏コメントありがとうございます!
ご指摘の通り、let と ?. の役割を明確に書けていなかったので、説明を修正しました。