本文已同步宣布于我的微信大众号,查找 代码说 即可重视,欢迎与我沟通交流。

类托付

托付机制是一种十分灵活的语言特性,它能够让咱们将目标的某些特点或办法托付给其他目标来处理。示例:

interface ISay {
    fun sayHello()
}
class DelegateImp : ISay {
    override fun sayHello() {
        println("sayHello from DelegateImp")
    }
}
//delegate传入DelegateImp()
class RealImp (val delegate : ISay) : ISay {
    override fun sayHello() {
        delegate.sayHello()
    }
}
  • RealImp 类将 sayHello() 办法的完成托付给了 DelegateImp 目标,从而完成了代码复用和模块化。
  • 当调用 RealImpsayHello() 办法时,实际上是调用了 DelegateImp 目标的 sayHello()办法。

Kotlin 中经过运用 by 要害字进行托付,上述 RealImp 的完成办法能够直接经过下面的办法来替代:

class RealImp2 : ISay by DelegateImp()  //办法1
class RealImp3(delegate: ISay) : ISay by delegate //办法2

上述两种办法的成果是相同的,能够看到 by 要害字后面的表达式是为了得到 ISay 接口的实例目标。最终履行:

RealImp(DelegateImp()).sayHello() //办法1
RealImp2().sayHello() //办法2
RealImp3(DelegateImp()).sayHello() //办法3

上述履行成果都是相同的:

sayHello from DelegateImp

特点托付

除了上一节中的接口类托付,Kotlin 还支撑特点托付,语法模板如下:

val/var <特点名>: <类型> by <表达式>

接口是把接口办法托付出去,那特点要托付什么呢?很简单,关于一个特点,无非有两个操作:获取特点以及修正特点,也便是对应的 get()/set()

特点托付即是将对应的 get()/set() 操作分发给指定托付目标getValue()/setValue() 办法履行;当然,如果是 val 润饰的特点,只需要供给 getValue() 即可。示例:

class Delegate {
    //对应特点中的get(),表明获取数据
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef${property.name}"
    }
    //对应特点中的set(),表明设置数据,只要var的特点会有
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef , ${property.name} , $value")
    }
}
  • thisRef: 请求其值的目标,即被托付目标的实例
  • property: 被托付目标特点的元数据。包含了被托付目标特点的称号、类型、可见性等信息。
  • valuesetValue() 中要设置的值。

运用示例

class DelegateProperty {
    var p1: String by Delegate()
}
fun main() {
   val property = DelegateProperty()
   println(property.p1) //getValue()
   property.p1 = "小马快跑" //setValue()
}
履行成果:
DelegateProperty@6d5380c2, p1  //getValue()
DelegateProperty@6d5380c2 , p1 , 小马快跑 //setValue()

上述 Delegate中的 getValue()/setValue()办法需要咱们手动编写,有点麻烦,Kotlin为咱们供给了ReadOnlyPropertyReadWriteProperty接口:

//只读
public fun interface ReadOnlyProperty<in T, out V> {
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
//读写都支撑
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

运用示例:

class DelegateR : ReadOnlyProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "getValue:$thisRef${property.name}"
    }
}
class DelegateRW : ReadWriteProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "getValue:$thisRef${property.name}"
    }
    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("setValue:$thisRef${property.name}$value")
    }
}
class DelegateProperty {
    val p2: String by DelegateR()
    var p3: String by DelegateRW()
}
fun main() {
   val property = DelegateProperty()
   println(property.p2) //p2取值
   println(property.p3) //p3取值
   property.p3 = "小马快跑" //p3设值
}
履行成果:
getValue:DelegateProperty@77a567e1,p2
getValue:DelegateProperty@77a567e1,p3
setValue:DelegateProperty@77a567e1,p3,小马快跑

能够看到利用ReadOnlyPropertyReadWriteProperty完成的成果跟手动完成的成果是共同的,但是比照手动编写更便利、更方便。

延迟托付 (by lazy)

//1
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
//2
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

lazy() 是接受一个 lambda 表达式, 并回来一个 Lazy <T> 实例的函数,回来的实例能够作为完成延迟特点的托付: 第一次调用特点的 get() 会履行已传递给 lazy()lambda 表达式并记载成果; 后续调用直接回来已经记载的成果。示例:

val lazyView by lazy {
  println("我只要第一次初始化的时候才履行哟~")
  "Come on"
}
//调用:
println("第1次:${property.lazyView}")
println("第2次:${property.lazyView}")
//履行成果:
我只要第一次初始化的时候才履行哟~
第1次:Come on
第2次:Come on

从成果能够看出只要第一次履行时走到了 lambda 表达式中的逻辑,第二次履行时直接回来第一次生成的成果。 别的再调用 lazy() 函数时能够传入一个 LazyThreadSafetyMode 参数,有下面三个值,别离代表不同的意思:

  • LazyThreadSafetyMode.SYNCHRONIZED:该值只在一个线程中核算,而且所有线程会看到相同的值。
  • LazyThreadSafetyMode.PUBLICATION:能够在并发访问未初始化的Lazy实例值时调用多次,但只要第一次回来的值将被用作Lazy实例的值。
  • LazyThreadSafetyMode.NONE:不会有任何线程安全的确保以及相关的开支,当初始化与运用总是位于相同线程中时运用。

如果lazy 特点的求值时不传 LazyThreadSafetyMode 参数,那么默认情况下走的是是LazyThreadSafetyMode.SYNCHRONIZED形式。

运用事例

先编写一个顶级扩展函数,注意其内部运用 lazy 包裹:

fun <T : View> Activity.id(id: Int) = lazy {
    findViewById<T>(id)
}

接下来就能够运用了:

//Activity
class ViewPager2DispatchActivity : AppCompatActivity() {
    //注意看是在这儿运用的
    private val mTvTxNews: TextView by id(R.id.tv_tx_news)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_pager2_dispatch)
        mTvTxNews.text = "我已经初始化好了,能够运用了"
    }
}
//XML中(activity_view_pager2_dispatch.xml)
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv_tx_news"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

能够看到经过private val mTvTxNews: TextView by id(R.id.tv_tx_news) 声明后,下面就能够正常运用该TextView了,其原理本质上便是经过 by lazy 延迟初始化了该特点,这种写法不需要每次去调用findViewById()了,跟咱们运用ButterKnife 库的效果是相同的。同理,如果在Fragment中或其他场景中运用,继续写类似的扩展函数即可。 嗯,Kotlin语法糖真甜!

可观察特点(observable properties)

Delegates.observable()能够认为是一个特点监听器,当监听的特点变更时会收到通知。其接受两个参数:初始值initialValueonChange()函数,当特点被赋值后就会触发onChange(),内部有三个参数:被赋值的特点、旧值与新值

public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }

Delegates.observable() 能够协助咱们在特点值发生变化时自动履行一些操作,例如更新UI保存数据防抖动等。它适用于需要对特点值进行监听和处理的场景。

运用事例

这儿以完成一个防抖动功能为例:

class ExampleActivity : AppCompatActivity()  {
    private var lastClickTime by Delegates.observable(0L) { _, old, new ->
        // 在lastClickTime特点值发生变化时履行点击事情
        if (new - old > 1000) {
            onClick()
        }
    }
    override fun onClick(view: View) {
        //每次点击都会触发Delegates.observable()
        lastClickTime = System.currentTimeMillis()
    }
    private fun onClick() {
        // 点击事情代码
    }
}

上述代码中,在 lastClickTime 特点值发生变化时,判别两次点击时间的间隔是否大于 1 秒:如果是,则履行点击事情;如果不是,什么都不做。如此经过 Delegates.observable 便完成了防抖动能力。

在特点被赋新值生效之前想截获赋值,能够运用 vetoable() 替代 observable()onChange()中会回来一个Boolean值来决定是否生效本次赋值

特点之间的托付

Kotlin 1.4 开始,一个特点能够把它的 gettersetter 托付给另一个特点,被托付的特点归于顶层或普通类特点都可。

为将一个特点托付给另一个特点,能够运用 :: 限定符,例如,this::delegateMyClass::delegate

运用事例
class DelegateProperty {
    @Deprecated("Use [newName] instead", ReplaceWith("newName"))
    var oldName by this::newName //this能够省略
    var newName = ""
}
fun main() {
    val property = DelegateProperty()
    property.oldName = "2023"
    println("oldName: ${property.oldName}")
    println("newName: ${property.newName}")
}
//履行成果:
oldName: 2023
newName: 2023

当想要以一种向后兼容的办法重命名一个特点的时候:引入一个新的特点、 运用 @Deprecated 注解来注解旧的特点、并托付其完成。

欢迎扫描下方二维码或查找微信大众号 代码说, 重视我的微信大众号检查最新文章~

Kotlin | 10分钟搞定by委托机制