本文已同步宣布于我的
微信大众号
,查找代码说
即可重视,欢迎与我沟通交流。
类托付
托付机制是一种十分灵活的语言特性,它能够让咱们将目标的某些特点或办法
托付给其他目标
来处理。示例:
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
目标,从而完成了代码复用和模块化。 - 当调用
RealImp
的sayHello()
办法时,实际上是调用了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: 被托付目标特点的元数据。包含了
被托付目标
特点的称号、类型、可见性
等信息。 -
value:
setValue()
中要设置的值。
运用示例:
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
为咱们供给了ReadOnlyProperty
、ReadWriteProperty
接口:
//只读
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,小马快跑
能够看到利用ReadOnlyProperty
、ReadWriteProperty
完成的成果跟手动完成的成果是共同的,但是比照手动编写更便利、更方便。
延迟托付 (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()
能够认为是一个特点监听器,当监听的特点变更时会收到通知。其接受两个参数:初始值initialValue
与onChange()函数
,当特点被赋值后就会触发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
开始,一个特点能够把它的 getter
与 setter
托付给另一个特点,被托付的特点归于顶层或普通类
特点都可。
为将一个特点托付给另一个特点,能够运用 :: 限定符
,例如,this::delegate
或 MyClass::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
注解来注解旧的特点、并托付其完成。
欢迎扫描下方二维码
或查找微信大众号 代码说
, 重视我的微信大众号检查最新文章~