经常在 Kotlin 的源码三方库中看到by关键字,这种写法就是委托,主要有两个应用场景,一个是委托类,另一个是委托属性,每个场景中又有不同的用法,我们可以对比 Java 的委托来学习 Kotlin 的委托。
 
委托(类委托、接口委托) 其实我们在 Java 和 Android 中经常会用到委托,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public  class  Delegated  {     interface  Base {         void  print () ;     }     class  BaseImpl  implements  Base {         private  final  int  x;         public  BaseImpl (int  x)  {             this .x = x;         }         @Override          public  void  print ()  {             System.out.println(x);         }     }     class  Derived  implements  Base {         private  final  Base base;         public  Derived (Base base)  {             this .base = base;         }         @Override          public  void  print ()  {             base.print();         }     } }
 
我们有一个接口Base,一个实现类BaseImpl。假如我们想要在实现类中添加一些方法,但又不想重新写一遍接口实现,第一种我们可以继承BaseImpl,另外一种就是实现接口Base,传入一个实现类的实例,将所有的接口请求都交给实现类的实例来完成。 虽然官方说委托模式已经证明是实现继承的一个很好的替代方式(The Delegation pattern has proven to be a good alternative to implementation inheritance) ,但选择权还是在大家手上,看情况而定,没有银弹。 那么在 kotlin 中应该怎么写呢?如果我们用 java 的思想来写,无非就是换换关键字,然后一坨模板代码,其实在 kotlin 中是可以通过by关键字零模板代码支持的
1 2 3 4 5 6 7 8 9 10 11 interface  Base   {     fun  print ()  }class  BaseImpl  (val  x: Int ) : Base {     override  fun  print ()   {         println(x)     } }class  Derived  (b: Base) : Base by  b
 
Derived的超类型列表中的by子句表示b将会在Derived中内部存储, 并且编译器将生成转发给b的所有Base的方法。 这个就是 kotlin 中的委托,有的地方也叫委托类或者委托接口。 这里有一点需要注意下,覆盖(override)是符合预期的:编译器会使用 override 覆盖的实现而不是委托对象中的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface  Base   {     fun  printMessage ()      fun  printMessageLine ()  }class  BaseImpl  (val  x: Int ) : Base {     override  fun  printMessage ()   { println(x) }     override  fun  printMessageLine ()   { println(x) } }class  Derived2  (b: Base) : Base by  b {     override  fun  printMessage ()   { println("abc" ) } }fun  main ()   {     val  b = BaseImpl(10 )     Derived2(b).printMessage()     Derived2(b).printMessageLine() }
 
我们在Derived2中覆写了printMessage这个方法,那么在调用的时候,就是用的我们覆写的方法。
属性委托 1 2 3 class  Example   {     var  p: String by  Delegate() }
 
语法是:val/var <属性名>: <类型> by <表达式>。在by后面的表达式是该委托, 因为属性对应的get()(与set())会被委托给它的getValue()与setValue()方法。 属性的委托不必实现接口,但是需要提供一个getValue()函数(对于var属性还有setValue())。 先从最简单的委托开始,最后再看自定义委托。
标准委托 借用官网的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 class  MyClass   {    var  newName: Int  = 0     @Deprecated("Use 'newName' instead" , ReplaceWith("newName" ) )    var  oldName: Int  by  this ::newName }fun  main ()   {    val  myClass = MyClass()            myClass.oldName = 42     println(myClass.newName)  }
 
这是一种最简单的委托方式。通过查看对应的 java 代码,发现其实就是对同一个成员变量的读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public  final  class  MyClass  {    private  int  newName;    public  final  int  getNewName ()  {       return  this .newName;    }    public  final  void  setNewName (int  var1)  {       this .newName = var1;    }        public  final  int  getOldName ()  {       return  this .newName;    }        public  final  void  setOldName (int  var1)  {       this .newName = var1;    } }
 
MyClass中的四个方法都是对newName这个字段的读写。
除此之外,委托属性可以是:
顶层属性 
同一个类的成员或扩展属性 
另一个类的成员或扩展属性 
 
比如
1 2 3 4 5 6 7 8 9 10 var  topLevelInt: Int  = 0 class  ClassWithDelegate  (val  anotherClassInt: Int )class  MyClass  (var  memberInt: Int , val  anotherClassInstance: ClassWithDelegate) {     var  delegatedToMember: Int  by  this ::memberInt     var  delegatedToTopLevel: Int  by  ::topLevelInt     val  delegatedToAnotherClass: Int  by  anotherClassInstance::anotherClassInt }var  MyClass.extDelegated: Int  by  ::topLevelInt
 
这种委托方式在我们做版本升级修改字段时是挺常用的,将旧字段委托给新字段,并将旧字段标记为过时。
懒加载委托 这种方式就是当我们首次访问这个属性的时候才会去初始化这个属性,从而避免不必要的资源消耗,和我们用 java 写单例模式的懒加载是一样的。 只会在首次访问的时候初始化这个属性,然后缓存起来,下次访问时直接返回。
1 2 3 4 5 6 7 8 9 val  lazyValue: String by  lazy {     println("computed!" )     "Hello"  }fun  main ()   {     println(lazyValue)     println(lazyValue) }
 
这里的 lazy 是一个高阶函数:
1 2 3 4 5 6 7 8 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)     }
 
可以看到,lazy接收一个 mode 参数,如果没有传入的话,默认是SynchronizedLazyImpl线程安全的:该值只在一个线程中计算,但所有线程都会看到相同的值。如果初始化委托的同步锁不是必需的,这样可以让多个线程同时执行,那么将LazyThreadSafetyMode.PUBLICATION作为参数传给 lazy()。 如果我们确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用LazyThreadSafetyMode.NONE模式。它不会有任何线程安全的保证以及相关的开销。 所以这个参数的选择也要看具体应用场景。
可观察委托 如果我们想要观察属性值的变化,可以使用Delegates.observable(),它接受两个参数:初始值与修改时处理程序(handler)。
1 2 3 4 5 6 class   ObservableItem  {     var  name :String by  Delegates.observable("initialValue" ){         prop,old,new->         println("$prop   $old  -> $new " )     } }
 
当我们给name赋值的时候,就会触发传入的处理程序,但这里我们只能观察到赋值,但并不能做拦截,如果想要截获取值并否决 ,可以使用vetoable()
可否决委托 如果我们想在观察属性值变化的同时决定是否使用新的值,可以使用Delegates.vetoable,同样的,它也接受两个参数:它接受两个参数:初始值与修改时处理程序(handler)。只不过这里的 handler 需要返回一个布尔值,告诉程序是否使用新值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class  VetoableItem  {     var  name :String by  Delegates.vetoable("initialValue" ){         prop,old,new->         println("$prop   $old  -> $new " )         new.length > 3      } }fun  main ()  {     val  vetoableItem = VetoableItem()     println(vetoableItem.name)     vetoableItem.name = "123"      println(vetoableItem.name)     vetoableItem.name = "1234"      println(vetoableItem.name) }
 
在这里,只有当new的长度大于 3 时,我们才会将new赋值给name,
将属性储存在映射中 一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者执行其他“动态”任务的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class  MapItem  (map: Map<String,Any?>){     val  name: String by  map     val  age:Int  by  map     val  address:String by  map }fun  main ()  {   val  map:Map<String,Any?> = mapOf(     "name"  to "xuan" ,     "age"  to 18 ,   )   val  mapItem = MapItem(map)   println("${mapItem.name}   ${mapItem.age} " )   println("${mapItem.name}   ${mapItem.age}   ${mapItem.address} " ) }
 
这里需要注意,假如我们传入的map里面没有对应属性,当程序运行时,这个属性没有被使用是没问题的,比如上面打印name和age。但是当我们使用这个属性的时候,就是上面打印address,会抛出异常Key address is missing in the map..另外一方面,我们将传入的 map 的值声明为了可空,这就意味着在调用出传入了空值,比如"address" to null,,代码是可以运行的,但对address这个属性做处理的时候会报空指针异常,这些都是需要额外注意的地方。 还有一点需要注意,如果是对于var属性,需要将Map替换成MutableMap,但是这样的话它们两个可是双向绑定的哟,比如下面这种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class  MutableMapItem  (map:MutableMap<String,Any?>){     var  name: String by  map     var  age: Int  by  map     var  address: String by  map }fun  main ()   {       val  map:MutableMap<String,Any?> = mutableMapOf(         "name"  to "xuan" ,         "age"  to 18 ,         "address"  to "beijing"      )     val  mutableMapItem = MutableMapItem(map)     println("${mutableMapItem.name}   ${mutableMapItem.age}  ${mutableMapItem.address} " )     println(map)     mutableMapItem.name = "huang"      println("${mutableMapItem.name}   ${mutableMapItem.age}  ${mutableMapItem.address} " )     println(map)     mutableMapItem.name = "yuan"      println("${mutableMapItem.name}   ${mutableMapItem.age}  ${mutableMapItem.address} " )     println(map) }
 
局部属性委托 可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:
1 2 3 4 5 6 7 8 fun  example (computeFoo: () -> Int )   {     val  memoizedFoo by  lazy(computeFoo)     val  someCondition = false      if  (someCondition && memoizedFoo>0  ) {         println(memoizedFoo+1 )     } }
 
memoizedFoo变量只会在第一次访问时计算。 如果someCondition失败,那么该变量根本不会计算。
自定义委托 先看一下自定义委托的要求有哪些,示例是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class  Resource class  Owner   {     var  varResource: Resource by  ResourceDelegate() }class  ResourceDelegate  (private  var  resource: Resource = Resource()) {     operator  fun  getValue (thisRef: Owner , property: KProperty <*>)  : Resource {         return  resource     }     operator  fun  setValue (thisRef: Owner , property: KProperty <*>, value: Any ?)   {         if  (value is  Resource) {             resource = value         }     } }
 
总结一下
对于var修饰的属性,我们必须要有getValue、setValue这两个方法,同时,这两个方法必须用operator关键字修饰。 
由于varResource是Owner,因此getValue、setValue这两个方法中的thisRef的类型,必须要是Owner或者是Owner的父类。一般来说,这三处的类型是一致的,当我们不确定委托属性会处于哪个类的时候,就可以将thisRef的类型定义为Any?。 
由于委托的属性是Resource类型,那么对于自定义委托中的getValue、setValue参数及返回值需要是String类型或者是它的父类 
 
我们可以把上面的代码当成模板代码,都是这样写就好了。如果觉得麻烦,可以使用标准库中的接口ReadOnlyProperty和ReadWriteProperty将委托创建为匿名对象,而无需创建新类。它们提供所需的方法:getValue()在ReadOnlyProperty中声明;ReadWriteProperty扩展了它并添加了setValue()。这意味着可以在需要ReadOnlyProperty时传递 ReadWriteProperty。 比如像这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fun  resourceDelegate (resource: Resource = Resource()  ) :ReadWriteProperty<Owner,Resource> =     object :ReadWriteProperty<Owner,Resource>{         var  curValue = resource         override  fun  getValue (thisRef: Owner , property: KProperty <*>)  : Resource=curValue                  override  fun  setValue (thisRef: Owner , property: KProperty <*>, value: Resource )   {             curValue = value         }     }class  Owner   {     val  readOnlyResource: Resource by  resourceDelegate()       var  readWriteResource: Resource by  resourceDelegate() }
 
提供委托 通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数, 那么会调用该函数来创建属性委托实例。比如在初始化之前检查一致性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class  ResourceDelegate <T > : ReadOnlyProperty <MyUI, T >  {     override  fun  getValue (thisRef: MyUI , property: KProperty <*>)  : T { ... } }class  ResourceLoader <T > (id: ResourceID<T>) {     operator  fun  provideDelegate (             thisRef: MyUI ,             prop: KProperty <*>     )  : ReadOnlyProperty<MyUI, T> {         checkProperty(thisRef, prop.name)                  return  ResourceDelegate()     }     private  fun  checkProperty (thisRef: MyUI , name: String )   { …… } }class  MyUI   {     fun  <T>  bindResource (id: ResourceID <T >)  : ResourceLoader<T> { …… }     val  image by  bindResource(ResourceID.image_id)     val  text by  bindResource(ResourceID.text_id) }
 
provideDelegate的参数与getValue的相同:
thisRef必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是它的超类型; 
property必须是类型KProperty<*>或其超类型。 
 
 
参考:
委托 Delegation 
 
已学习:
未学习:
关键字
object 
Unit 
Nothing 
inline,noinline,crossinline 
 
 
协程
启动 
挂起 
Job 
Context 
Channel 
Flow 
select 
并发、异常 
launch 
Dispatchers 
CoroutineScope