委托的基本理念是:操作对象将某段逻辑的处理工作,交给另外一个辅助对象去做。
1. 类委托
如果我们在代码中使用set,但set只是一个接口,我们需要借助它的实现类HashSet或者其它实现类。但是我们如果想要自己实现一个set接口的实现类,我们就需要set的方法都具体实现一遍,这样即麻烦又容易出现逻辑上的错误。但是借助委托模式,我们就可以这么写:
class MySet<T>(val helperSet: HashSet<T>) : Set<T>{
override val size: Int
get() = helperSet.size
override fun contains(element: T): Boolean {
return helperSet.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return helperSet.containsAll(elements)
}
override fun isEmpty(): Boolean {
return helperSet.isEmpty()
}
override fun iterator(): Iterator<T> {
return helperSet.iterator()
}
}
可以看到我们在自己实现MySet的时候,构造中需要用到HashSet实例作为辅助对象,我们内部的方法实现逻辑其实是交给了HashSet实例完成的。这其实就是一种委托。这时大家就会又疑问了,我们直接用HashSet不好吗?但如果我们要有特殊实现呢, 比如我们在判断isEmpty()的时候,我们就想返回false(没错,就是这么变态)。但是我们再想,如果我们的接口中待实现方法很多,每一个方法的实现我们都需要手动的去借助委托实例的方法去实现的话,我们的工作量仍然会很大。到此,我们又能感受Kotlin的强大之处了。Kotlin提供了“by”关键字,通过类委托的功能来解决我们手动委托工作量大的问题。写法如下:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{
override fun isEmpty(): Boolean {
return false
}
}
这样我们就可以手动的实现我们想实现的方法,其它的则会自动交给helperSet去实现,因为helperSet是HashSet,所以我们的MySet其它方法的实现就会和HashSet保持一致。这就是Kotlin中类委托所实现的功能。
2. 委托属性
类委托的核心思想是将一个类的具体实现委托给另一个类去完成,如果我们理解了类委托的思想,那么委托属性的思想也就不在话下了,委托属性的核心思想就是将一个属性的具体实现委托给另一个类去完成。代码如下:
class MyClass {
var p by Delegate()
}
这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了的Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。因此,我们还得对Delegate类进行具体的实现才行,代码如下所示:
class Delegate {
var propValue: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
这是一个标准的实现模版,在Delegate中必须声明getValue()和setValue()方法,并且都要使用operatorg关键字声明。整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClass中p属性的值时,就会调用Delegate类的getValue()方法。
getValue()方法参数说明:
- 第一个参数用于声明该Delegate类的委托功能可以在什么类中使用,这里表示仅可在MyClass中使用。
- 第二个参数:KProperty<*>是Kotlin中的一个属性操作类,可用于获取各种属性相关的值。另外星号表示的泛型意思是你不知道或不关心泛型的具体类型,类似于Java中的<?>。
setValue()的参数前两个和getValue()一样,第三个表示要赋给委托属性的值。
当我们给MyClass中的p属性赋值时,就会调用Delegate中的setValue方法,在使用p属性的值时,就会调用getValue方法。
3. lazy函数
我们先来看下lazy函数的用法:
class MainActivity : AppCompatActivity() {
val p : String by lazy {
Log.i("lazy","code inside lazy")
"lazy p"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener { Log.i("btn","btn print "+p) }
}
}
//I/lazy: code inside lazy
//I/btn: btn print lazy p
by lazy 代码块是Kotlin提供的一种懒加载技术,代码块中的代码并不会在一开始就执行,上面代码中,只有在用到p变量的时候,才会执行lazy代码块中的代码,对p进行初始化。
我们在了解了Kotlin的委托功能后,就可以来一步步看下lazy函数的原理了。我们已经知道by在Kotlin中是委托的一个关键字,那lazy又是什么呢?lazy其实只是一个高阶函数,在其内部创建并返回了一个Delegate对象,当我们调用p的时候,其实是在调用Delegate对象的getValue()方法,然后getValue()方法又会调用lazy函数传入的Lambda表达式,p的最终值就是lambda表达式的返回值。
接下来我们就手撸一个lazy函数:
//2.创建顶层方法,方便调用
fun <T> myLazy(block: () -> T) = MyLazy(block)
//1.创建委托类实现getValue方法
class MyLazy <T> (val block:() -> T){
var value : Any? = null
operator fun getValue(any: Any?, prop:KProperty<*>):T {
if (value != null) {
value = block()
}
return value as T
}
}
- 定义了泛型类MyLazy,它接收一个函数类型参数,返回类型是指定泛型。
- getValue()方法第一个参数指定为Any?类型,表示MyLazy的委托功能在所有类中都可以使用。
- 在使用value 时,如果value为空,就调用构造中传入的函数类型参数去取值。
然后我们就可以像使用lazy函数那样使用myLazy了。
class MainActivity : AppCompatActivity() {
val p : String by myLazy {
Log.i("lazy","code inside lazy")
"lazy p"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener { Log.i("btn","btn print "+p) }
}
}
//I/lazy: code inside lazy
//I/btn: btn print lazy p