类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
interface IDemo {
fun action1()
fun action2()
}
//被委托的类
class DemoImpl : IDemo {
override fun action1() {
println("do action1 ")
}
override fun action2() {
println("do action2")
}
}
//通过by建立委托类
class DemoImplDelegate1 (private val demo:IDemo):IDemo by demo
class DemoImplDelegate1 :IDemo by DemoImpl()
fun main(args: Array) {
val demoImpl = DemoImpl()
DemoImplDelegate1(demoImpl).action1()
}
class DemoImplDelegate2(private val demo:IDemo):IDemo by demo {
override fun action1() {
println("delegate before")
demo.action()
println("delegate after")
}
fun action() {
}
}
class Test {
// 属性委托
var prop: String by Delegate()
}
委托属性的语法如下:
val/var <属性名>: <类型> by <表达式>
和类委托原理一样,被代理的逻辑就是这个属性的get/set方法。get/set会委托给被委托对象的setValue/getValue方法,因此被委托类需要提供setValue/getValue这两个方法。如果是val 属性,只需提供getValue。如果是var 属性,则setValue/getValue都需要提供。
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
interface ReadOnlyProperty {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
被委托类实现这两个接口其中之一就可以了,val 属性实现ReadOnlyProperty,var属性实现ReadWriteProperty。
// val 属性委托实现
class Delegate1: ReadOnlyProperty{
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "通过实现ReadOnlyProperty实现,name:${property.name}"
}
}
// var 属性委托实现
class Delegate2: ReadWriteProperty{
override fun getValue(thisRef: Any, property: KProperty<*>): Int {
return 20
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
println("委托属性为: ${property.name} 委托值为: $value")
}
}
// 测试
class Test {
// 属性委托
val d1: String by Delegate1()
var d2: Int by Delegate2()
}
fun main(args: Array) {
val test = Test()
println(test.d1)
println(test.d2)
test.d2 = 100
}
执行结果:
通过实现ReadOnlyProperty实现,name:d1
20
委托属性为: d2 委托值为: 100
val lazyValue: String by lazy {
println("只执行第一次!") // 第一次调用输出,第二次调用不执行
"你好"
}
fun main(args: Array) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
lazy()是接受一个Lambda表达式作为参数的函数,返回一个Lazy实例,返回实例可以作为延迟属性的委托:第一次调用get()会执行已传递给lazy()的lambda表达式并记录结果,后续调用只是返回记录的结果。
lazy()还可以接受LazyThreadSafeMode类型的参数:
val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("只执行第一次!")
"你好"
}
默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。
Delegates是一个代理单例对象,里面有notNull、observable、vetoable静态方法,每个方法返回不同的类型代理对象。
如果想要观察一个属性的变化过程,可以将属性委托给Delegates.observable.
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(args: Array) {
val user = User()
user.name = "第一次赋值" //旧值:初始值 -> 新值:第一次赋值
user.name = "第二次赋值" //旧值:第一次赋值 -> 新值:第二次赋值
}
Delegates.observable()函数接受两个参数:第一个是初始化值,第二个是属性值变化时的回调处理器。回调有三个参数:被赋值的属性property、旧值oldValue、新值newValue。
public inline fun observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty =
object : ObservableProperty(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
ReadWriteProperty位于package kotlin.properties,用力一瞥源码
public abstract class ObservableProperty(initialValue: T) : ReadWriteProperty {
private var value = initialValue
...
protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit {}
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
...
this.value = value
afterChange(property, oldValue, value)
}
}
vetoable与observable一样可以观察属性值的变化,不同的是vetoable可以通过处理器函数来决定属性值是否生效。
var vetoableProp: Int by Delegates.vetoable(0){
_, oldValue, newValue ->
// 如果新的值大于旧值,则生效
newValue > oldValue
}
fun main(args: Array) {
val user = User()
println("vetoableProp=$vetoableProp") //0
vetoableProp = 10
println("vetoableProp=$vetoableProp") //10
vetoableProp = 5
println("vetoableProp=$vetoableProp") //10
vetoableProp = 100
println("vetoableProp=$vetoableProp") //100
}
如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。
class Foo {
var notNullBar: String by Delegates.notNull()
}
//println(foo.notNullBar)
foo.notNullBar = "bar"
println(foo.notNullBar)
notNull 适用于那些无法在初始化阶段就确定属性值的场合,如果属性在赋值前就被访问的话则会抛出异常。
notNull()首先是一个方法,返回的是一个NotNullVar属性代理实例:
private class NotNullVar() : ReadWriteProperty {
private var value: T? = null
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
实际上可以理解在访问器getter加了一层判空的代理实现。
相比Java,Kotlin属性定义时需要额外的属性初始化的工作。但是可能某个属性的值在开始定义时并不知道,需要执行到后面的逻辑才能拿到。可以实现的方法:
属性使用方式 | 优点 | 缺点 |
---|---|---|
初始化赋默认值 | 使用简单 | 仅适用基本数据类型 |
属性代理notNull | 适用基本数据类型和引用类型 | 1、存在属性初始化必须在属性使用之前的问题;2、不支持外部注入工具将它直接注入到Java字段中 |
lateinit修饰属性 | 仅适用于引用类型 | 1、存在属性初始化必须在属性使用之前的问题;2、不支持基本数据类型 |
在一个映射(map)里存储属性的值,使用映射实例自身作为委托来是爱心委托属性:
class Site(val map: Map) {
val name: String by map
val url: String by map}
fun main(args: Array) {
// 构造函数接受一个映射参数
val site = Site(mapOf(
"name" to "koltin委托",
"url" to "https://xiaomi.f.mioffice.cn/ "
))
// 读取映射值
println(site.name) //koltin委托
println(site.url) //https://xiaomi.f.mioffice.cn/
}
也可以替换成可变的Map:
class Site(val map: MutableMap) {
val name: String by map
val url: String by map
}
fun main(args: Array) {
var map:MutableMap = mutableMapOf(
"name" to "koltin委托",
"url" to "https://xiaomi.f.mioffice.cn/"
)
val site = Site(map)
println(site.name)
println(site.url) //同上
}
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
可以将局部变量声明为委托属性,memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。接口PropertyDelegateProvider(不用实现这个接口,提供provideDelegate方法即可)。
class People {
val name: String by DelegateProvider(0)
val address: String by DelegateProvider(1)
}
class DelegateProvider(delegateType:Int) {
private val mDelegateType = delegateType
operator fun provideDelegate(thisRef: People, property: KProperty<*>): ReadOnlyProperty {
println("I'm in provideDelegate.")
checkProperty()
if(mDelegateType == 0 {
return RealDelegate1()
} else {
return RealDelegate2()
}
}
private fun checkProperty() {
val random = Random.Default
if (!random.nextBoolean()) {
throw RuntimeException("failed to create delegate.")
}
}
}
class RealDelegate : ReadOnlyProperty {
override fun getValue(thisRef: People, property: KProperty<*>): String {
return "kotlin"
}
}
class RealDelegate2: ReadOnlyProperty {
override fun getValue(thisRef: People, property: KProperty<*>): String {
return "Java"
}
}
必须提供一个provideDelegate的方法,参数和getValue相同,方法签名:
operator fun provideDelegate(thisRef: T, property: KProperty<*>): RealOnlyProperty
或者
operator fun provideDelegate(thisRef: T, property: KProperty<*>): ReadWriteProperty
运行结果
fun main() {
val people = People()
println(people.name)
println(people.address)
}
委托在Kotlin中占有很重要的应用,特别是属性委托比如lazy延迟初始化使用。委托模式是一项技巧,其他的几种设计模式如:策略模式、状态模式和访问者模式都是委托模式的具体场景应用。
by关键字是一种约定,是对委托类的方法的约定,就是简化函数调用。这里的by约定简化了属性的get和set方法的调用,当然委托类需要定义相应的函数:getValue,setValue。