😀

Kotlinのスコープ関数(let, run, apply, also)を整理してみた

に公開
2

はじめに

普段、業務でWebシステムのサーバサイドをKotlinで開発しています。

Kotlinには letapply といった「スコープ関数」と呼ばれる便利な機能があります。一方で、実務の中では let を使う場面が特に多く、他のスコープ関数は用途が限定されている印象です。

そこで今回は、自分自身の備忘録も兼ねて、これらのスコープ関数の違いと使い分けを整理してみました。

スコープ関数の違いを整理する

スコープ関数は5種類(let, run, with, apply, also)ほどありますが、大きく違うのは以下の二つです。

  1. オブジェクトの参照方法: ブロック内で「it」として扱うか、「this」として扱うか
  2. 戻り値: ブロックの「最後の行の結果」を返すか、「オブジェクト自身」を返すか

これらを表にまとめると以下のようになります。

関数 オブジェクト参照 戻り値 主な用途のイメージ
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
}

まとめ・所感

今回、改めてスコープ関数を整理してみて、それぞれの違い(itthis か、戻り値は何か)が整理できました

ただ、自分の業務コードに当てはめて考えてみると、「意外と let 以外は登場頻度が低いかも?」というのが正直な感想です。
特に apply については、最初は「オブジェクトの構築に使えそう」と思いましたが、 data class を使うことが多く、コンストラクタで値を渡す形の方がイミュータブルで扱いやすい場面が多いと感じました。

Discussion

hondayahondaya

実用的な例がまとまってて良い記事だと思いました!

letの利用用途をNull checkと書いてるんですが、Null checkできているのは、Nullableに対する?.の部分でsafe callできてるからかなと思ったので、「letの用途」という意味ではちょっとニュアンスが別で取られてしまうかな?と思いましたmm

let自体はT -> R への(変換)処理をするscope関数だと思うので、
サンプルで記載している処理も、emailがnon nullを担保して(safe call)、Userへの処理をしている(letの中の処理)と思うので、letが担っている実体としては、email -> userの処理をscopeで実現しているという説明の方が合うかなと思いました🙏

2
Tsu02NaTsu02Na

コメントありがとうございます!
ご指摘の通り、let と ?. の役割を明確に書けていなかったので、説明を修正しました。

1