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```という文字が使用されることが多い印象。(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
…任意の型T
はBundle
型 -
block()
…関数オブジェクトを実行している部分はputString("HOGE", "hoge”)
、putInt("FUGA", 1)
、putBoolean("IS_PIYO", true)
-
return this
…返り値の部分は実際に値をput
したBundle
ということ。
参考
いつもお世話になっている本
Kotlin イン・アクション