kotlin-委托
[toc]
委托是什么
委托又可以称为代理。为其他对象提供一种代理以控制对这个对象的访问,简单的说就是在访问和被访问对象中间加上的一个间接层,以隔离访问者和被访问者的实现细节。
类委托
类委托其实对应于Java中的代理模式
interface Base{
fun text()
}
//被委托的类(真实的类)
class BaseImpl(val x :String ) : Base{
override fun text() {
println(x)
}
}
//委托类(代理类)
class Devices (b :Base) :Base by b
fun main(){
var b = BaseImpl("我是真实的类")
Devices(b).text()
}
输出
我是真实的类
可以看到,委托类(代理类)持有真实类的对象,然后委托类(代理类)调用真实类的同名方法,最终真正实现的是方法的是真实类,这其实就是代理模式
kotlin中委托实现借助于by关键字,by关键字后面就是被委托类,可以是一个表达式
接口委托
如果接口有很多方法,子类就必须全部实现。
interface Api{
fun a()
fun b()
fun c()
}
class ApiWrapper:Api{
override fun a() {
}
override fun b() {
}
override fun c() {
}
}
kotlin可以找个对象代替你实现它,我们只需要实现需要的方法即可。
class ApiWrapper(val api:Api):Api by api{
override fun a() {
println("判断是否登录")
api.a()
}
}
设计一个好玩的例子
class SupperArray<T>(val list: MutableList<T> = mutableListOf(), val map: MutableMap<String, T> = mutableMapOf()) :
MutableList<T> by list, MutableMap<String, T> by map{
override fun clear() {
list.clear()
map.clear()
}
override fun isEmpty(): Boolean {
return list.isEmpty() && map.isEmpty()
}
override val size: Int
get() = list.size+map.size
override fun toString(): String {
return "$list $map"
}
}
这是一个超级集合,包含一个list和一个map。这里用了代理,不需要实现全部的方法。只需要提供必要的方法。
fun main(args: Array<String>) {
val supperArray = SupperArray<String>()
supperArray["a"]= "hello"
supperArray.add("world")
println(supperArray.toString())
}
打印
[world] {a=hello}
属性委托
属性委托和类委托一样,属性的委托其实是对属性的==set/get==方法的委托,把set/get方法委托给==setValue/getValue==方法,因此被委托类(真实类)需要提供==setValue/getValue==方法,val属性只需要提供==setValue==方法
属性委托语法:
val/var <属性名>: <类型> by <表达式>
class B {
//委托属性
var a : String by Text()
}
//被委托类(真实类)
class Text {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "属性拥有者 = $thisRef, 属性的名字 = '${property.name}' 属性的值 "
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("属性的值 = $value 属性的名字 = '${property.name}' 属性拥有者 = $thisRef")
}
}
fun main(){
var b = B()
println(b.a)
b.a = "ahaha"
}
输出
属性拥有者 = com.example.lib.weituo.B@27fa135a, 属性的名字 = 'a' 属性的值
属性的值 = ahaha 属性的名字 = 'a' 属性拥有者 = com.example.lib.weituo.B@27fa135a
上面的例子可以看到 ,属性a 委托给了Text,而且Text类中有setValue和getValue,所以当我们调用属性a的get/set方法时候,会委托到Text的setValue/getValue上,上面的例子可以看出来,里面有几个参数介绍一下:
thisRef:属性的拥有者
property:对属性的描述,是KProperty<*>类型或者父类
value:属性的值
更加简单的实现属性委托
每次实现委托都要写getValue/setValue方法,这就比较麻烦了,系统为我们提供了接口,方便我们重写这些方法,ReadOnlyProperty和ReadWriteProperty
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
Koltin 标准库中提供的几个委托
延迟属性(lazy properties):其值只在访问时初始化,再次调用直接返回结果
可观察属性(observable properties):监听器会收到此属性的变更通知
把多个属性映射到(Map)中,而不是存在单个字段
延迟属性lazy
lazy()接收一个lambda,返回Lazy实例,返回的实例可以作为实现延迟属性的委托,仅在第一次调用属性进行初始化
class Lazy {
val name :String by lazy {
println("第一次调用初始化")
"aa" }
}
fun main(){
var lazy =Lazy()
println(lazy.name)
println(lazy.name)
println(lazy.name)
}
输出
第一次调用初始化
aa
aa
aa
lazy委托参数
SYNCHRONIZED:添加同步锁,使lazy延迟初始化线程安全
PUBLICATION:初始化的lambda表达式,可以在同一时间多次调用,但是只有第一次的返回值作为初始化值
NONE:没有同步锁,非线程安全
1. by lazy是通过属性代理来实现的懒加载,只在第一次使用的时候才会去执行表达式,并且只会执行一次。
2. by lazy默认是线程安全的,内部通过双重判断锁来保证只执行一次代码块赋值
3. 当能够确定不会发生在多线程中的时候,可通过lazy(LazyThreadSafetyMode.NONE) { ... }来避免加锁。
lazy 原理
通过前面对属性委托的简单介绍,我们也明白了属性委托的机制。by后面跟着的肯定是一个对象,也就是委托对象,该对象负责属性的get/set,所以lazy这个方法最终肯定会返回一个对象。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
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)
}
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)
lazy方法一共有三个,我们最常用的是第一个。从代码中可以看出,lazy方法最终是创建的Lazy<T>的实例,这个实例也就是属性委托的对象。
public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
Lazy一共有三个子类,其中我们使用的第一个方法返回的是SynchronizedLazyImpl,这是Lazy的其中一个实现。
internal object UNINITIALIZED_VALUE
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
// 委托的属性的值由_value记录,初始值是单例对象UNINITIALIZED_VALUE
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
// 已初始化则直接返回对应值
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
// 未初始化的,执行传递进来的lambda参数进行赋值
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
private fun writeReplace(): Any = InitializedLazyImpl(value)
}
SynchronizedLazyImpl是Lazy的一个子类,可以看到在value属性的get方法中,会去判断是否已经初始化过,若是没有初始化,则会调用initializer去进行初始化,赋值给_value。若是已经初始化过,则会直接返回 _value的值。因此by lazy就能通过这种方式去实现懒加载,并且只加载一次。
从代码中可以看出,SynchronizedLazyImpl是保证了线程安全的。是通过DCL方式来保证的安全,能够确保在多线程下也只会执行一次代码块。
但是我们说过,要实现可读属性委托,必须实现getValue方法。而在SynchronizedLazyImpl中,并没有实现getValue方法,而是只有value属性的get方法。这里猜测编译器应该是对Lazy有特殊的处理,而通过实验,实现了Lazy接口的对象确实可以作为只读属性的委托对象。而其他接口即使与Lazy一模一样,实现它的对象也不能作为属性委托对象。
可观察属性Observable委托
可以观察一个属性的变化过程
class Lazy {
var a : String by Delegates.observable("默认值"){
property, oldValue, newValue ->
println( "$oldValue -> $newValue ")
}
}
fun main(){
var lazy =Lazy()
lazy.a = "第一次修改"
lazy.a = "第二次修改"
}
输出
默认值 -> 第一次修改
第一次修改 -> 第二次修改
vetoable委托
vetoable和Observable一样,可以观察属性的变化,不同的是vetoable可以决定是否使用新值
class C {
var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
println("oldValue = $oldValue -> oldValue = $newValue" )
newValue > oldValue
}
}
fun main() {
var c = C()
c.age = 5
println(c.age)
c.age = 10
println(c.age)
c.age = 8
println(c.age)
c.age = 20
println(c.age)
}
输出
oldValue = 0 -> oldValue = 5
5
oldValue = 5 -> oldValue = 10
10
oldValue = 10 -> oldValue = 8
10
oldValue = 10 -> oldValue = 20
20
当新值小于旧值,那么就不生效,可以看到第三次设的值是8,小于10就没有生效
属性储存在Map中
class D(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(){
var d = D(mapOf(
"name" to "小明",
"age" to 12
))
println("name = ${d.name},age = ${d.age}")
}
输出
name = 小明,age = 12