LoginSignup
7
3

More than 5 years have passed since last update.

Kotlinのapplyをじっくりと読む

Last updated at Posted at 2018-08-04

Kotlinのapplyを理解していく過程で色々勉強になったので、その過程をそのまま✏️。

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

kotlin.internal.contracts.Standard.ktより)



ひとつずつ何が書いてあるか理解していく。

publicの部分

public は可視性修飾子。どこからでも参照可能であることを意味している。

public


inlineの部分

inline はinline関数にするための修飾子。

inline

関数型を引数にもつ関数を定義するとき、実行時に無名クラスをたくさん作ってオーバーヘッドを起こさないようにする目的で使用される。inline修飾子をつけておくと、コンパイラがinline宣言されている関数を呼び出しているコードの箇所に直接関数本体を置換する。
(確認したい場合は実際にデコンパイルしてみると良い。)

funの部分

fun は関数宣言。関数を定義することを意味している。

fun


<T>の部分

<T> は型パラメータ。慣習でTという文字が使用されることが多い印象。(Typeの”T”なのかな?と勝手に思っている。知っている人教えてください)

<T>



型パラメータのシンプルな例。クライアントによって、Tは任意の型になる。

sample("hoge") // この場合だと、任意の型TはString型と推論される。
sample(1) // この場合だと、任意の型TはInt型と推論される。

fun <T> sample(t: T) {
    println(t?.hashCode())
}


T.apply()の部分

T.apply() はKotlinの拡張関数。任意の型Tの拡張関数applyを定義するという意味。

T.apply()

拡張関数は[型名].[関数名]という書き方をする。
もちろん拡張関数は、任意の型T出なくても作ることができる。
参考までにString型で拡張関数を作ってみると、こんな感じ。

println("hoge".toQuestion()) // 拡張関数を使って「hoge」を「hoge?」にしてみる

fun String.toQuestion(): String {
    return this + "?"
}


block: T.() -> Unitの部分

block:blockは引数の名前。 T.() -> Unitは関数型。関数型も型の一種で、少し特徴的な印象はあるが、ぜひInt型やString型などと同じくらいの親近感で接してあげたい。
applyという拡張関数はblockという名前の、T.() -> Unitという関数型を引数にとるという意味。

block: T.() -> Unit

関数型は[引数の型] -> [戻り値の型]という書き方をする。

T.() -> Unit

これはT.()という引数をとり、戻り値はないという関数型という意味になる。

では、T.()という引数とは、いったい何者なのか。これは任意の型Tをレシーバとすることを意味している。
レシーバオブジェクトにはthis.でアクセスすることができる。

"hoge".apply({
    this.toUpperCase() // レシーバオブジェクトにはthis.でアクセスできる。
})

突然ラムダ式を記載したが、ラムダは{ }で囲われた処理を関数の引数に渡すことができる。
レシーバ付きラムダは任意のレシーバオブジェクトのメソッドにアクセスできる。

ちなみにここでは明示的に()の中にラムダを書いて、関数の引数に渡したけど、
関数の引数がラムダ唯一であれば、丸括弧()は省略できるので、こう書き換えられる。

"hoge".apply {
    this.toUpperCase()
}


: Tの部分

: Tは関数の返り値。applyという拡張関数は任意の型Tを返すという意味。

: T


ここで一旦整理

ここまでで、一旦整理。

つまりこれ↓は、

public inline fun <T> T.apply(block: T.() -> Unit): T

任意の型Tの拡張関数をapplyという名前で、どこからでもアクセス可能なinline関数として定義。
拡張関数の引数にはblockという名前の関数型をとり、その関数型は任意の型Tのレシーバを引数にとり、戻り値はなし。
拡張関数の戻り値は任意の型T




続いてapplyの中身、その前に再掲

続いて関数の中身を見ていく。その前にapplyの定義を見る度に、だいぶページをスクロールしないといけなくなるので、復習も兼ねて再掲。

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


block()の部分

apply 関数に渡された引数の関数オブジェクトを実行している。

block()

apply を使用するときにラムダで関数オブジェクトを渡している。まさにその渡されたラムダ{ }の中身を実行している場所。

"hoge".apply {
    toUpperCase() // ここを実行している
}


return thisの部分

return this は関数の戻り値。つまりここでは、任意の型Tのオブジェクトが返却される。

return this



Androidアプリではこんな感じで使用することが多い印象。

val bundle = Bundle().apply {
    putString("HOGE", "hoge")
    putInt("FUGA", 1)
    putBoolean("IS_PIYO", true)
}

apply の定義と照らし合わせると、

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

Bundle.apply{ }{ }囲われた部分が関数オブジェクトとしてapply関数に渡されるので、apply関数の定義に書いてあるblock()では、渡された{ }の中の処理が順番に全部実行されることになる。

  • T …任意の型TBundle
  • block() …関数オブジェクトを実行している部分はputString("HOGE", "hoge”)putInt("FUGA", 1)putBoolean("IS_PIYO", true)
  • return this …返り値の部分は実際に値をputしたBundle

ということ。

参考

いつもお世話になっている本
Kotlin イン・アクション

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3