LoginSignup
12
8

More than 5 years have passed since last update.

Kotlinのチュートリアルをやってみた⑤

Posted at

前回に引き続き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の公式の記述を貼っておきます。
スクリーンショット 2017-05-21 18.28.05.png

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よりだいぶ楽だなと思ってしまいました。

12
8
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
12
8