Kotlinのスコープ関数のapplyについて

採用はこちら

Kotlinのapplyは、オブジェクト自身をthisとして扱いながら処理を行い、そのオブジェクト自身を返すスコープ関数です。

特に、オブジェクトの初期化プロパティの設定をまとめて書きたいときによく使われます。

たとえば、次のような場面です。

data class User(
    var name: String = "",
    var age: Int = 0
)

val user = User().apply {
    name = "Shinichi"
    age = 30
}

このコードでは、User()で作成したオブジェクトに対してnameageを設定し、その後設定済みのUserオブジェクト自身が返されます。

目次

applyの基本的な特徴

applyには、次の2つの重要な特徴があります。

ラムダ内で対象オブジェクトを this として扱う

applyのブロックは、レシーバー付きラムダです。

そのため、ラムダの中では対象オブジェクトのメンバにthis経由でアクセスできます。

val user = User().apply {
    this.name = "Shinichi"
    this.age = 30
}

ただし、Kotlinではthisを省略できるため、通常は次のように書きます。

val user = User().apply {
    name = "Shinichi"
    age = 30
}

この「メンバに直接触れるような書き方」が、applyの大きな使いやすさです。

戻り値はラムダの結果ではなく、オブジェクト自身

applyは、ブロックの最後の式を返しません。

返すのは常に、処理対象のオブジェクト自身です。

val user = User().apply {
    name = "A"
    age = 20
    "hello"
}

この場合でも返るのは "hello" ではなく、User オブジェクトです。

この性質があるため、apply「設定して、そのままそのオブジェクトを使いたい」という場面に向いています。

applyが向いている場面

オブジェクト生成直後の初期化

もっとも典型的な使い方です。

val person = Person().apply {
    firstName = "Taro"
    lastName = "Yamada"
    age = 25
}

オブジェクトを作ってすぐ設定する処理を、ひとまとまりで書けます。

Viewや設定オブジェクトの構築

Androidや設定クラスなど、プロパティをいくつも設定する場面と相性が良いです。

val textView = TextView(context).apply {
    text = "Hello"
    textSize = 16f
    isAllCaps = false
}
val config = MailConfig().apply {
    host = "smtp.example.com"
    port = 587
    username = "admin"
    password = "secret"
}

Mutableなコレクションの構築

val numbers = mutableListOf<Int>().apply {
    add(1)
    add(2)
    add(3)
}

これも「作る」「中身を入れる」「そのまま受け取る」という流れが自然に書けます。

Intentやリクエストオブジェクトの作成

val intent = Intent(context, DetailActivity::class.java).apply {
    putExtra("userId", 1001)
    putExtra("userName", "Shinichi")
}
val request = SearchRequest().apply {
    keyword = "Kotlin"
    page = 1
    pageSize = 20
}

applyは初期化専用ではない

applyは初期化でよく使われますが、新しく作ったオブジェクトにしか使えないわけではありません

既存のオブジェクトに対して使うこともできます。

user.apply {
    age += 1
    name = name.uppercase()
}

つまり、applyの本質は「オブジェクトをthisとして操作し、そのオブジェクト自身を返すこと」であり、その結果として初期化処理と相性が良い、という理解が正確です。

applyと他のスコープ関数の違い

Kotlinのスコープ関数は似ているため、違いを整理すると理解しやすくなります。

apply

  • オブジェクトの受け取り方: this
  • 戻り値: オブジェクト自身
  • 主な用途: 初期化、設定、構築
val user = User().apply {
    name = "A"
}

run

  • オブジェクトの受け取り方: this
  • 戻り値: ラムダの結果
  • 主な用途: オブジェクトを使った処理結果を返したいとき
val description = User().run {
    name = "A"
    age = 20
    "$name is $age years old"
}

この場合の戻り値はUserではなく文字列です。

let

  • オブジェクトの受け取り方: it
  • 戻り値: ラムダの結果
  • 主な用途: 変換、null安全呼び出し
val length = "hello".let {
    it.length
}

also

  • オブジェクトの受け取り方: it
  • 戻り値: オブジェクト自身
  • 主な用途: 補助処理、ログ出力、副作用的な処理
val user = User().also {
    println("created: $it")
}

applyalsoの違い

applyalsoはどちらもオブジェクト自身を返すという点では共通しています。

違いは、ラムダ内での扱い方と、読み手に伝わる意図です。

apply

  • thisで扱う
  • プロパティ設定や構築処理に向いている
val user = User().apply {
    name = "Shinichi"
    age = 30
}

also

  • itで扱う
  • ログ出力や確認処理など、「ついでの処理」に向いている
val user = User().also {
    println("User created: $it")
}

なお、これは文法上の強制ではなく、慣用的な使い分けです。

alsoでプロパティを設定することもできますが、通常はapplyのほうが意図が伝わりやすくなります。

applyとnull安全呼び出し

applyはnull安全呼び出しともよく組み合わされます。

val user: User? = getUser()

user?.apply {
    age += 1
    name = "Updated Name"
}

この場合、userがnullでなければブロックが実行されます。

また、戻り値も確認しておくと大切です。

val updatedUser = user?.apply {
    age += 1
}

このupdatedUserの型はUser?です。

apply自体はオブジェクト自身を返しますが、?.を使っているため結果全体はnullableになります。

ネストしたapplyは注意が必要

applyは便利ですが、入れ子にしすぎると読みにくくなります。

理由は、どのthisを指しているのか分かりにくくなるからです。

val result = A().apply {
    B().apply {
        C().apply {
            // どの this を指しているのか分かりにくい
        }
    }
}

このような場合は、ラベル付きthisを使ったり、別変数に分けたり、必要に応じてalsoを使ったりすると可読性が上がります。

val a = A().apply outer@{
    val b = B().apply inner@{
        println(this@outer)
        println(this@inner)
    }
}

applyの内部イメージ

applyは概念的には次のような関数だと考えるとわかりやすいです。

inline fun <T> T.apply(block: T.() -> Unit): T {
    this.block()
    return this
}

ポイントは2つです。

  • T.() -> Unit
    これは、Tをレシーバーとして使うラムダ
  • return this
    これにより、ラムダ結果ではなくオブジェクト自身が返る

この仕組みを理解すると、runletとの違いも見えやすくなります。

よくある誤解

誤解1: applyはラムダの最後の値を返す

違います。

返すのは常に、対象オブジェクト自身です。

誤解2: applyは初期化のときしか使えない

違います。

既存オブジェクトをまとめて操作したいときにも使えます。

誤解3: applyalsoは同じ

完全には同じではありません。

戻り値はどちらもオブジェクト自身ですが、applyは構築・設定向き、alsoは補助処理向きという違いがあります。

実務での判断基準

applyを使うべきなのは、次のような場面です。

  • オブジェクトを作った直後に設定をまとめたい
  • DTO、設定クラス、View、Intentなどを組み立てたい
  • 最後にそのオブジェクトをそのまま受け取りたい

逆に、別の値を返したい場合はrunletのほうが自然です。

val text = User().run {
    name = "A"
    "$name"
}

このように、「オブジェクトが欲しいのか、処理結果が欲しいのか」で使い分けると判断しやすくなります。

まとめ

applyは、オブジェクトをラムダのレシーバーとして扱い、そのオブジェクトに対する設定や操作を書いたあと、オブジェクト自身を返すスコープ関数です。

本質は次の2点です。

  • ラムダ内で対象をthisとして扱える
  • 戻り値はラムダ結果ではなく、対象オブジェクト自身

そのため、applyは特に初期化・設定・構築に向いています。

たとえば次のコードは、applyの典型例です。

val user = User().apply {
    name = "Shinichi"
    age = 30
}

これは単に短く書いているだけではなく、「このオブジェクトを組み立てている」という意図を読み手に明確に伝えられる書き方でもあります。

以上、Kotlinのスコープ関数のapplyについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次