携手创作,共同生长!这是我参加「日新计划 8 月更文挑战」的第28天,点击检查活动详情

托付其实是一种规划形式,但Kotlin把此特性编写进了语法中,能够方便开发者快速运用,本篇也来详细解说下关于Kotlin中特点托付的运用

托付对应的关键字是by

特点托付

先讲下特点托付吧,首要,复习下kotlin中设置set和get办法

默许的set和get咱们能够躲藏,实践上一个简略的类代码如下:

class Person {
	var personName = ""
	// 这是默许的 get/set(默许是躲藏的)
	get() = field
	set(value) {
		field = value
	}
}

这里详细知识点能够检查之前所说Kotlin学习快速入门(3)——类 继承 接口 – Stars-One的杂货小窝

当然,如果是数据bean类,咱们会将get和set办法躲藏(或者运用data关键字来声明一个数据类)

若咱们需求在get或set办法的时分做一下逻辑处理,比如说上面的personName字段,咱们只允许接纳长度小于等于10的字符串,超越10长度的字符串就不接纳(即不设置新数值),则是应该这样写:

class Person{
    var personName = ""
        // 这是重写的 get/set
        get() = "PersonName $field"
        set(value) {
            field = if (value.length <= 10) value else field
        }
}

然后,咱们再延伸出来,如果此规矩不止使用于personName字段,还可用到其他类的字段中,这个时分便是运用到特点托付。

简略描述: 咱们将此规矩抽取出来,需求使用到此规矩的字段的get/set办法托付给规矩去做,这就叫特点托付

推迟加载(懒加载)

在开端讲特点托付之前,先说明下推迟加载

Kotlin中供给了lazy办法,运用by+lazy{}联用,咱们就完成推迟加载(也可称作懒加载)

fun main() {
    val demo = Demo()
    val textContent = demo.textContent
    val result = demo.textContent.substring(1)
    println(result)
    println("打印:$textContent")
}
class Demo{
    val textContent by lazy { loadFile() }
}
fun loadFile(): String {
    println("读取文件...")
    //模仿读取文件返回数据
    return "读取的数据"
}

这里的关键词by出现在特点名后面,表示特点托付,行将特点的读和写托付给另一个目标,被托付的目标有必要满足一定的条件:

  • 关于 val 润饰的只读变量进行特点托付时,被托付的目标有必要完成getValue()接口,即界说如何获取变量值。
  • 关于 var 润饰的读写变量进行特点托付时,被托付目标有必要完成getValue()setValue()接口,即界说如何读写变量值。

lazy()办法,接纳一个lambda函数,返回值是一个Lazy目标,所以就能够简写成上面的姿态,其只完成了getValue()接口,所以,当你测验将textContent改为var类型,IDE会提示报错!!

也是由于这点,归于推迟加载的字段,是不可被再次修改了,所以采用lazy懒加载的方法,其实便是单例形式

lazy函数默许是线程安全的,而且是经过加锁完成的。如果你的变量不会涉及到多线程,那么请务必运用LazyThreadSafetyMode.NONE参数,避免不必要的功能开支,如下示例代码

val name:String by lazy(LazyThreadSafetyMode.NONE) { "Karl" }

Delegates.vetoable

还记得上述咱们要完成的规矩吗,其实Kotlin中已经有了几个默许的托付规矩供咱们快速运用(上述的lazy其实也是一个)

Delegates.vetoable()的规矩便是上述规矩的通用封装,解释为:

但会在特点被赋新值生效之前会传递给Delegates.vetoable()进行处理,根据Delegates.vetoable()的返回的布尔值判别要不要赋新值。

如下面比如:

class Person {
    var personName by Delegates.vetoable("") { property, oldValue, newValue ->
        //当设置的新值满足条件,则会设置为新值
        newValue.length <= 10
    }
}

Delegates.notNull

设置字段不能为null,不过想不到详细的使用情景

class Person {
    var personName by Delegates.notNull<String>()
}

Delegates.observable

运用Delegates.observable能够帮咱们快速完成观察者形式,只需字段数值产生改变,就会触发

class Person{
    var age by Delegates.observable(0){ property, oldValue, newValue ->
        //这里能够写相关的逻辑
        if (newValue >= 18) {
            tip = "已成年"
        }else{
            tip = "未成年"
        }
    }
    var tip =""
}

上面的比如就比较简略,设置age一起更新提示,用来判别是否成年

 val person = Person()
    person.age = 17
    println(person.tip)

弥补-自界说托付

上述都是官方界说好的一些景象,但如果不满足咱们的需求,这就需求自界说托付了

官方供给了两个基础类供咱们自界说托付运用:

ReadWriteProperty 包含get和set办法,对应var关键字 ReadOnlyProperty 只有get办法,对应val关键字

PS:实践上,咱们自己随意创立个托付类也是能够的,不过这样写不太标准,所以咱们一般直接完成官方给的上述两个类即可

Kotlin学习快速入门—— 属性委托

ReadWriteProperty和ReadOnlyProperty都需求传两个泛型,分别为R,T

  • R 持有特点的类型
  • T 字段类型

或许上面描述不太理解,下面给个简略比如,Person类中有个name字段(String),首字母需求大写:

class Person {
    var name by NameToUpperCase("")
}
class NameToUpperCase(var value:String) :ReadWriteProperty<Person,String>{
    //NameToUpperCase类中默许有个特点字段,用来存数据
    override fun getValue(thisRef: Person, property: KProperty<*>): String {
        return this.value
    }
    override fun setValue(thisRef: Person, property: KProperty<*>, value: String) {
        //在设置数值的时分,将第一个字母转为大写,一般推荐在setValue里编写逻辑
        this.value = value.substring(0,1).toUpperCase()+value.substring(1)
    }
}

个人观点,一般在setValue的时分进行设置数值比较好,由于getValue作操作的话,会触发多次,处理的逻辑复杂的话或许会浪费功能…

当然,再提醒下,上面的逻辑也能够直接去字段里的setValue()里边改,运用托付的目的便是方便抽取出去供其他类运用相同的规矩,减少模板代码

PS: 如果你的托付不是针对特定的类,R泛型能够改为Any

类托付

这个一般与多态一起运用,不过个人想不到有什么详细的使用情景,暂时做下简略的记录

interface IDataStorage{
    fun add()
    fun del()
    fun query()
}
class SqliteDataStorage :IDataStorage{
    override fun add() {
        println("SqliteDataStorage add")
    }
    override fun del() {
        println("SqliteDataStorage del")
    }
    override fun query() {
        println("SqliteDataStorage query")
    }
}

假设现在咱们有个MyDb类,查询的办法与SqliteDataStorage这个里的办法有所区别,但其他办法都是没有区别,这个时分就会用到类托付了

有以下几种托付的运用方法

1.托付类作为构造器形参传入(常用)

class MyDb(private val storage:IDataStorage) : IDataStorage by storage{
    override fun add() {
        println("mydb add")
    }
}
val db = MyDb(SqliteDataStorage())
db.add()
db.query()

输出成果:

mydb add
SqliteDataStorage query

如果是全部都是托付给SqliteDataStorage的话,能够简写为这样:

class MyDb(private val storage:IDataStorage) : IDataStorage by storage

2.新建托付类目标

class MyDb : IDataStorage by SpDataStorage(){
    override fun add() {
        println("mydb add")
    }
}

这里测试的效果与上文一样,不在重复赘述

参阅

  • kotlin-特点托付_南郭竽的博客-CSDN博客_kotlin特点托付
  • Kotlin 基础 | 托付及其使用_普通网友的博客-CSDN博客
  • Kotlin修炼指南(五)—Delegates_eclipse_xu的博客-CSDN博客
  • kotlin类托付、特点托付_水煮鱼在飞的博客-CSDN博客_kotlin 类托付