20.Kotlin属性委托

Kotlin属性委托(delegated property)

示例代码

class MyDelegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String = "$thisRef,you delegated property name is ${property.name}"
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) = println("${thisRef},new value is $value")
}

class MyPropertyClass {
    var str: String by MyDelegate()
}

fun main(args: Array<String>) {
    val myPropertyClass = MyPropertyClass();

    myPropertyClass.str = "hello world"
    println(myPropertyClass.str)
}

输出

com.leofight.kotlin4.MyPropertyClass@439f5b3d,new value is hello world
com.leofight.kotlin4.MyPropertyClass@439f5b3d,you delegated property name is str

语法是: val/var <属性名>: <类型> by <表达式>。在 by 后面的表达式是该委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。

有4种情况在实际开发中比较有用:
1.延迟属性。
2.可观测属性
3.非空属性
4.map属性

延迟属性

延迟属性: 指的是属性只在第一次访问的时候才会计算,之后则会将之前的计算结果缓存起来供后续调用

lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证和相关的开销。

LazyThreadSafetyMode

1.SYNCHRONIZED:默认情况下,延迟属性的计算是同步的:值只会在一个线程中得到计算,所有线程都会使用相同的一个结果。

2.PUBLICATION:如果不需要初始化委托的同步,这样多个线程可以同时执行。

3.NONE:如果确定初始化操作只会在一个线程中执行,这样会减少线程安全方面的开销。

示例代码:

val myLazyValue: Int by lazy {
    println("hello world")

    30
}

fun main(args: Array<String>) {
    println(myLazyValue)
    println(myLazyValue)
}

输出

hello world
30
30

非空属性

示例代码

class MyPerson {
    var address: String by Delegates.notNull<String>()
}

fun main(args: Array<String>) {
    val myPerson = MyPerson();

    myPerson.address = "suzhou"
    println(myPerson.address)
}

输出

suzhou

如果注释掉myPerson.address = "suzhou",则输出

Exception in thread "main" java.lang.IllegalStateException: Property address should be initialized before get.
    at kotlin.properties.NotNullVar.getValue(Delegates.kt:55)
    at com.leofight.kotlin4.MyPerson.getAddress(HelloKotlin4.kt)
    at com.leofight.kotlin4.HelloKotlin4Kt.main(HelloKotlin4.kt:15)

notNull适用于那些无法在初始化阶段就确定属性值的场合。

可观察属性(Observable)

示例代码

class Person{
    var age: Int by Delegates.observable(20){
        prop,oldValue,newValue -> println("${prop.name},oldValue: $oldValue,newValue: $newValue")
    }
}

class Person2{
    var age: Int by Delegates.vetoable(20){
        prop,oldValue,newValue -> when{
            oldValue <= newValue -> true
            else -> false
        }

    }
}

fun main(args: Array<String>) {

    val person = Person();
    person.age = 30
    person.age = 40

    println("-------")

    val person2 = Person2();
    println(person2.age)

    person2.age = 40
    println(person2.age)

    println("=========")

    person2.age = 30
    println(person2.age)

输出

age,oldValue: 20,newValue: 30
age,oldValue: 30,newValue: 40
-------
20
40
=========
40

Delegates.observable接收两个参数:初始值与修改处理器。
处理器会在我们每次对属性赋值时得到调用(在赋值完成之后被调用)
处理器本身接收3个参数:被赋值的属性本身,旧的属性与新的属性

Delegates.vetoable的调用时机与Delegates.observable相反,它是在对属性赋值之前被调用,根据Delegates.vetoable的返回结果是true还是false,来决定是否真正对属性进行赋值。

map委托

将属性存储到map中

一种常见的应用场景是将属性值存储到map当中。
这通常出现在JSON解析或是一些动态行为。
在这种情况中,你可以将map实例作为委托,作为类中属性的委托。

map中的key的名字要与类中属性的名字保持一致

示例代码

class Student(map: Map<String, Any?>) {

    val name: String by map

    val address: String by map

    val age: Int by map

    val birthday: Date by map
}


class Student2(map: MutableMap<String, Any?>) {

    var address: String by map
}

fun main(args: Array<String>) {
    val student = Student(mapOf(
            "name" to "zhangsan",
            "address" to "beijing",
            "age" to 20,
            "birthday" to Date()
    ))

    println(student.name)
    println(student.address)
    println(student.age)
    println(student.birthday)

    println("--------")

    val map: MutableMap<String, Any?> = mutableMapOf(
            "address" to "bejing"
    )

    val student2 = Student2(map)

    println(map["address"])
    println(student2.address)

    println("---------")

    student2.address = "shanghai"

    println(map["address"])
    println(student2.address)
}

输出

zhangsan
beijing
20
Tue May 01 16:55:19 CST 2018
--------
bejing
bejing
---------
shanghai
shanghai

关于属性委托的要求
对于只读属性来说(val修饰的属性),委托需要提供一个名为getValue的方法,该方法接收如下参数:
- thisRef,需要是属性拥有者相同的类型或者其父类型(对于扩展属性来说,这个类型指的被扩展的那个类型)
- property,需要是KProperty<*>类型或是其父类型

getValue方法需要返回与属性相同的类型或是其子类型

对于可变属性来说(var修饰的属性),委托需要再提供一个名为serValue的方法,该方法需要接收如下参数:
- thisRef,与getValue的thisRef要求一致
- property,与getValue方法的property要求一致。
- new value,需要与属性的类型相同或其父类型

getValue与setValue方法既可以作为委托类的成员方法实现,也可以作为其扩展方法来实现。

这两个方法都必须要标记为operator关键字。对于委托类来说,它可以实现ReadOnlyProperty或是ReadWriterProperty接口,这些接口包含了相应的getValue与setValue方法。同时,对于委托类来说,也可以不去实现这两个接口,而是自己单独实现相应的getValue与setValue方法。

委托转换规则

对于每个委托属性来说,Kotlin编译器在底层会生成一个辅助的属性,然后将原有属性的访问委托给这个辅助属性。
比如说,对于属性prop来说,Kotlin编译器所生成的隐含的属性名为prop$delegate属性,然后对原有的prop属性的访问器的访问都只是委托给这个额外的,Kotlin编译器所生成的辅助属性。

提供委托(providing a delegate)

通过定义provideDelegate operator,我们可以扩展委托的创建逻辑过程。如果对象定义了provideDelegate方法,那么该方法就会被调用来创建属性委托实例。

示例代码

class PropertyDelgate : ReadOnlyProperty<People, String> {
    override fun getValue(thisRef: People, property: KProperty<*>): String {
        return "hello world"
    }
}

class PeopleLauncher {
    operator fun provideDelegate(thisRef: People, property: KProperty<*>): ReadOnlyProperty<People, String> {
        println("welcome")

        when (property.name) {
            "name", "address" -> return PropertyDelgate()
            else -> throw Exception("not valid name")
        }
    }
}

class People {
    val name: String by PeopleLauncher()
    val address: String by PeopleLauncher()
}

fun main(args: Array<String>) {
    val people = People();

    println(people.name)
    println(people.address)
}

输出

welcome
welcome
hello world
hello world
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 本文是在学习和使用kotlin时的一些总结与体会,一些代码示例来自于网络或Kotlin官方文档,持续更新... 对...
    竹尘居士阅读 3,272评论 0 8
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,171评论 9 118
  • 简述 在java中一些属性的具有相同的行为怎么办,抽象出类然后再去依赖调用,而在Kotlin中只需要一个by关键字...
    i校长阅读 2,621评论 0 4
  • 躺在床上 回想那些年被自己肆意糟蹋的青春 为所欲为 或许人在年幼的时候都会犯错 任性、暴躁、狂妄、无知⋯ 或许这就...
    上官楠阅读 335评论 3 4
  • 近日,山东艺术学院舞蹈系的6名女生,身穿舞蹈服、旗袍和汉服,拍摄毕业照,留作青春最后的纪念。拍摄过程中,还摆出各式...
    d3c28a312600阅读 463评论 0 1