前回に引き続きSwiftエンジニアがKotlinのチュートリアルを進めます。今回はDelegated propertiesの章です。
https://try.kotlinlang.org/#/Examples/Delegated%20properties/Custom%20delegate/Custom%20delegate.kt
自分が実装した分のリポジトリ
https://github.com/akatsuki174/KotlinTutorial
上記のDelegatedProperties.ktファイル
Delegated properties
Custom delegate
import kotlin.reflect.KProperty
class Example {
var p: String by Delegate()
override fun toString() = "Example Class"
}
class Delegate() {
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
println("$value has been assigned to ${prop.name} in $thisRef")
}
}
fun main(args: Array<String>) {
val e = Example()
println(e.p) // getterが呼ばれる
e.p = "NEW" // setterが呼ばれる
}
プロパティの後ろにby
を記述してdelegateを実装しています。プロパティのdelegatesはinterfaceで実装されている必要はありません。プロパティがval
で宣言されている時はgetValue()
が、プロパティがvar
で宣言されている時はgetValue()
に加えてsetValue()
も用意する必要があります。
Lazy property
class LazySample {
val lazy: String by lazy {
println("computed!")
"my lazy"
}
}
fun main(args: Array<String>) {
val sample = LazySample()
// 1回目の呼び出し時にはクロージャ内の処理が行われるが
// 2回目以降の呼び出し時には単に初回の評価結果が返される
println("lazy = ${sample.lazy}") // クロージャ内のprintln()文も実行される
println("lazy = ${sample.lazy}") // 初回評価結果である"my lazy"しかprintされない
}
名前の通り遅延プロパティを表します。最初にgetで呼ばれた時はlazy()が渡されたラムダ式が実行され、結果を保持します。それ以降にgetで呼ばれた時はその保持していた結果を返します。
デフォルトの動作では遅延プロパティの評価は同期され、処理は1つのスレッド、参照は全てのスレッドからできるという状態になります。もし初期化デリゲート同期が必要なく、複数スレッドで同時に実行できるようにしたい場合はLazyThreadSafetyMode.PUBLICATION
をlazy()関数のパラメータとして渡します。もし初期化が常にシングルスレッドで行われることがわかっているなら、LazyThreadSafetyMode.NONE
モードが使えます。
参考までにこのenumの公式の記述を貼っておきます。
Observable property
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("no name") {
d, old, new ->
println(d) // -> var Person.name: kotlin.String
println("$old - $new")
}
}
fun main(args: Array<String>) {
val user = User()
println("最初 = ${person.name}") // -> 最初 = no name
person.name = "Carl" // -> no name - Carl
println("代入後 = ${person.name}") // -> 代入後 = Carl
}
observable関数は2つの引数(初期値と修正のための値(ブロックの部分))を取ります。ハンドラはnameに値を割り当てる度に呼ばれ、3つのパラメータ(割り当てられている値、古い値、新しい値)を持っています。
observable()の代わりにvetoable()を使用するとプロパティへの代入前にラムダ式が実行され、代入を行うかどうかを値として返します。これにより、代入を拒否することができます。
class Person {
var age: Int by Delegates.vetoable(3) {
d, old, new ->
println("$old -> $new")
old < new // 新しい値の方が大きくなかったら拒否
}
}
val person = Person()
println("age: 最初 = ${person.age}") // -> age: 最初 = 3
person.age = 4 // -> 3 -> 4
println("age: 代入後 = ${person.age}") // -> age: 代入後 = 4
person.age = 2 // -> 4 -> 2
println("age: 代入拒否後 = ${person.age}") // -> age: 代入拒否後 = 4
NotNull property
import kotlin.properties.Delegates
class User {
var name: String by Delegates.notNull()
fun init(name: String) {
this.name = name
}
}
fun main(args: Array<String>) {
val user = User()
// user.name -> IllegalStateException
user.init("Carl")
println(user.name)
}
Kotlinでは初期化されていない非抽象プロパティを持つことはできません。nullで初期化することもできますが、アクセスするたびに確認する必要があります。Kotlinにはこれを解決するdelegateがあり、あるプロパティに書き込みする前に読み込みをしようとすると例外が発生します。割り当て後は期待通りに動きます。
Properties in map
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println("name = ${user.name}, age = ${user.age}")
}
プロパティをmapに保存する例です。Jsonをパースしたりその他動的なことをやる際によく使われています。mapから値を取り出す時は文字列のキーを用います。
もちろん、 read-onlyのmapではなくMutableなmapにしてvarを使えば割り当て時にmap変更を可能にすることができます。
class Dog(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
val dog = Dog(mutableMapOf(
"name" to "Pochi",
"age" to 2
))
println("name = ${dog.name}, age = ${dog.age}") // -> name = Pochi, age = 2
dog.name = "Taro"
dog.age = 3
println("name = ${dog.name}, age = ${dog.age}") // -> name = Taro, age = 3
感想
notNullが地味に便利そう
Swiftでも初期化されていない非抽象プロパティを持つことはできず、値を最初から割り当てる必要がなければIntは0でStringは""で初期化したり、オプショナルで書いたりしていますが、それをせず、notNullを明示できるのは良いなと思いました。
アップデートが楽
IntelliJ IDEAの話になりますが諸々のアップデートが楽です。Kotlinのバージョンアップ、プラグインのアップデートの連絡は画面右下に出てきて、それをタップすればやってくれます。Xcodeよりだいぶ楽だなと思ってしまいました。