この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
毎日Kotlinシリーズです。
このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO
問題
String and map builders | Try Kotlin
Extension function literals are very useful for creating builders, e.g.:
fun buildString(build: StringBuilder.() -> Unit): String {
val stringBuilder = StringBuilder()
stringBuilder.build()
return stringBuilder.toString()
}
val s = buildString {
this.append("Numbers: ")
for (i in 1..3) {
// 'this' can be omitted
append(i)
}
}
s == "Numbers: 123"
Add and implement the function 'buildMap' with one parameter (of type extension function) creating a new HashMap, building it and returning it as a result. The usage of this function is shown below.
狙い
ここで考えて欲しい問題の意図はなんだろうか?
Builderでよく使うパターンですが、見慣れないとややこしく見えてしまうかもしれません。
thisがなんだったか思い出して解いてみよう。
[毎日Kotlin] Day10. Extension functions(拡張関数)
解答例
fun <K, V> buildMap(build: HashMap<K, V>.() -> Unit): Map<K, V> {
val map = HashMap<K, V>()
map.build()
return map
}
buildMapの引数がbuild: HashMap<K, V>.() -> Unitになっています。buildMap内で使用する拡張関数buildと考えるとわかりやすいです。
使用例をみると、一時的な拡張関数とみれば、thisがHashMap<K, V>であることがわかりますね。HashMap<K, V>のメンバ関数であるputが使用できます。
fun usage(): Map<Int, String> {
return buildMap {
put(0, "0") // this.putと同じ
for (i in 1..10) {
put(i, "$i") // this.putと同じ
}
}
}
別解: apply
Kotlinではちょっと便利なスコープ関数というあります。こちらを使う場面は沢山あります。詳細は割愛し、すでに良記事がありますので、ぜひこちらを参照。
今回の例でもつかえます。applyを使うのがよいと思います。
fun <K, V> buildMap(build: HashMap<K, V>.() -> Unit): Map<K, V> = HashMap<K, V>().apply(build)
apply at 1.1.0 · JetBrains/kotlin
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
別解: ラムダ式
通常のラムダ式でも同様のことできます。ラムダ式の場合は、引数として渡してあげればよいですね。
fun <K, V> buildMap(build: (HashMap<K, V>) -> Unit): Map<K, V> {
val map = HashMap<K, V>()
build(map)
return map
}
ただし、この場合は、HashMap<K, V>は、レシーバーから引数になったので使用の仕方が異なります。
fun usage(): Map<Int, String> {
return buildMap {
it.put(0, "0") // thisではなくit
for (i in 1..10) {
it.put(i, "$i") // thisではなくit
}
}
}
しかし、(HashMap<K, V>) -> UnitとHashMap<K, V>.() -> UnitはKotlin上では等価です。
つまり、以下のこともできます。
fun <K, V> buildMap(build: (HashMap<K, V>) -> Unit): Map<K, V> = HashMap<K, V>().apply(build)
あとがき
Day39.でまたお会いしましょう。