第十五讲 Kotlin之强大的委托机制
前介
委托是 Kotlin
中新引入的一种概念,它能给我们开发中带来很多的遍历,接下来由我给大家揭开它的面纱,并且给大家提供几种好用的实战经验。
多继承
在 Java
的世界中是只能单继承的。在 Kotlin
的世界中是存在多继承的(我在前面好像说过)。
什么?多继承?
Kotlin
的产物不是class
吗?那就必定要受限于Java
的语言的约定啊?那就不应该存在多继承的概念啊?
其实 Kotlin
的确不存在多继承,但是存在一种委托机制,可以达到类似多继承的关系,我们俗称曲线救国。
要实现通过委托来完成多继承,委托对象必须实现一个接口。
/**
* 定义人类
*/
open class People(val name: String) {
fun eat() {
println("$name chifan ")
}
fun sleep() {
println("$name sleep")
}
}
/**
* 定义程序员接口
*/
interface ICode {
fun codeing()
}
/**
* 可以写代码技能
*/
class Code(val name: String) : ICode {
override fun codeing() {
println("$name 写代码")
}
}
/**
* 可以看到,我通过接口+by 关键词 来进行了一次委托
*/
class CodePeople(name: String) : People(name), ICode by Code(name)
fun main() {
/**
* 创建 CodePeople 对象
*/
val codePeople = CodePeople("阿文")
/**
* 调用吃饭睡觉方法
*/
codePeople.eat()
codePeople.sleep()
/**
* 调用委托过来的方法
*/
codePeople.codeing()
}
通过上面的代码,我们发现 codePeople
对象既能调用父类 People
的方法,也能调用到委托的 Code
的 codeing
方法,有那么一点多继承的感觉。哪我们能重写 codeing
方法吗?
class CodePeople(name: String) : People(name), ICode by Code(name){
override fun codeing() {
println("$name 写牛逼的代码")
}
}
发现是可以重写 codeing
方法了,越来越像多继承的感觉了。
多继承的本质
Java
是不可能存在多继承的。Kotlin
是在 Java
的基础上建立的帝国,所以也不能丢了根本,哪它是如何实现的呢?我们看下反编译的 Java
代码。
public final class CodePeople extends People implements ICode {
// $FF: synthetic field
private final Code ?delegate_0;
public CodePeople(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super(name);
// 可以注意到这里,当我们构造 CodePeople 对象的时候,创建了一个 Code 对象
this.?delegate_0 = new Code(name);
}
// 实现了接口方法,并且调用了 Code 的 codeing 方法
public void codeing() {
this.?delegate_0.codeing();
}
}
原来,如此简单,其实就是一个 静态代理模式
。这下知道为啥,所有的委托都需要实现一个接口了吧。那么也就是说委托的对象,只能调用接口的委托对象方法。
/**
* 定义程序员接口
*/
interface ICode {
fun codeing()
}
/**
* 可以写代码技能
*/
class Code(val name: String) : ICode {
override fun codeing() {
println("$name 写代码")
}
/**
* 我们多增加一个方法,但是不在接口中
*/
fun zhuangB(){
println("$name 开始装B")
}
}
/**
* 可以看到,我通过接口+by 关键词 来进行了一次委托
*/
class CodePeople(name: String) : People(name), ICode by Code(name)
fun main() {
/**
* 创建 CodePeople 对象
*/
var codePeople = CodePeople("阿文")
/**
* 能调用 装B 方法吗? 编译器报错
*/
codePeople.zhuangB()
}
通过上面的代码,我们验证了理论,其实多继承就是
静态代理模式
,所以说只能调用接口中的方法,不能调用非接口的方法。例如Code
类的zhuangB
方法CodePeople
对象是无权调用的。
委托变量
还记的 val
变量 和 var
变量的区别吗?我们复习下,其实 Kotlin
定义的变量都隐藏着方法。
val userToken:String
get() {
return "阿文"
}
var userAge:Int = 0
get() {
return 18
}
set(value) {
field = value + 2
}
我相信大家,应该了解这些变量的
get
和set
方法的作用,我这里就不叙述啦!若忘记了,请翻阅我的变量篇章。
有啥用呢?这里有很大的学问,例如我一个遍历的来源和设置都是通过一个文件(例如:Andorid
中的 SharedPreferences
或数据库),我们可以尝试重写隐藏的 get
和 set
方法。
当然这种方案是不建议的,我们可以通过委托来解决这个问题,Kotlin
为我们提供了一个委托类。 ReadWriteProperty
和 ReadOnlyProperty
接口类。
实战,通过 ReadWriteProperty
来实现,获取用户的 Token
的获取和保存。
object User{
/**
* 例如 定义 userToken
* 它的来源都是在一个文件中,注意我这里使用了委托,将 userToken 的 get 和 set 房委托给了 TokenProperty 对象
*/
var userToken: String by TokenProperty()
}
/**
* 定义委托的对象
*/
class TokenProperty : ReadWriteProperty<User, String> {
override fun getValue(thisRef: User, property: KProperty<*>): String {
// todo 可以从文件读取token
val token = loadFileToekn()
return token
}
override fun setValue(thisRef: User, property: KProperty<*>, value: String) {
// todo 将 token 保存到文件
saveToken(value)
}
private fun saveToken(value: String) {
}
private fun loadFileToekn():String{
// 模拟从文件读取 token
return "阿文"
}
}
fun main() {
/**
* 如果我们登陆成功了,我们要设置 Token 直接使用
*
* 根据委托规则,他会调用 TokenProperty 对象的 setValue 方法
*/
User.userToken = "阿文"
/**
* 若想获取 直接获取
*
* 根据委托规则,他会调用 TokenProperty 对象的 getValue 方法
*/
val toekn = User.userToken
print(toekn)
}
通过上面的例子,我们应该看到了将变量委托的好处了吧?我们可以直接通过调用变量或赋值的方式,增加我们一些其他的逻辑。我这个例子委托的是
var
变量,如果我们想委托的是val
变量,我们只需要实现ReadOnlyProperty
接口。
当然委托接口方法还有thisRef
和property
大家可以自行查阅,是啥东西,其实很简单。
通过委托来完美实现 Andorid
的 SharedPreferences
封装代码 (参考)[wenyingzhi.com/mu-lu-2/kot…]。
懒加载
实战中,我们经常遇到懒加载。例如:一个对象在用的时候再去创建,但是用的地点有很多,我们以前的逻辑都是在使用的地方加 null
判断,如为 null
就创建对象。这种脑壳疼的代码弄的我
我想 Kotlin
的设计师也为此烦恼吧?因此通过委托的机制,为我们提供了一个懒加载的方法 lazy
。
/**
* 需要懒加载的对象
*
* 懒加载的对象必须是 val 变量?大伙想想为啥?
*/
val lazyObj by lazy {
Random()
}
fun main() {
/**
* 当我使用的时候才会去创建 lazyObj 对象
*/
val nextInt = lazyObj.nextInt()
print(nextInt)
}
看看是不是超级简单啊?只需要写 by lazy
,就能很简单的完成一个懒加载对象,并且再也不用在使用的地方加 null
判断了。
哪 lazy
的原理呢?其实就是一个委托。
妈的我记错了?我刚去看了下源码 by lazy
返回的是一个 Lazy
对象。我的天,我一直以为是实现了 ReadOnlyProperty
接口呢?
真相
新机呲挖一呲冒黑套呲!!
我最终看看了下 ReadWriteProperty
就是一个接口,那么 Kotlin
的 by
关键词到底是在干嘛呢?我们看下 ReadWriteProperty
定义。
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
可以发现 setValue
和 getValue
方法是通过 operator
重载操作符的。我们知道 by
关键词最终会调用者2个方法。也就是说使用 by
关键词,只需要重载 getValue
和 setValue
方法即可。
哪我们看下 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)
}
通过上面代码,我们注意到其实 lazy
还体贴的考虑到了,线程安全问题。接下来我们去追寻 SynchronizedLazyImpl
实现。
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
// 有一个 value 变量
override val value: T
get() {
// 这里面的内容,主要是判断当前懒加载的对象是否加载,如果加载了就直接返回
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
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)
}
通过上面的代码,我们发现奇怪没有重载 getValue
和 setValue
方法啊?最后我自己转念一想 SynchronizedLazyImpl
中存在一个 value
变量,不就默认存在 getValue
和 setValue
方法吗?也就是说这个 value
变量是关键,并且名字必须是 value
。
我们尝试自定义一个懒加载。
class Code {
fun eat(food: String = "米饭") {
synchronized(this) {
println("吃$food")
}
}
}
val c by myLazyCall {
Code()
}
fun <T> myLazyCall(block: () -> T) = MyLazy<T>(block)
class MyLazy<out T>(val block: () -> T) :Lazy<T>{
val tmp: T? = null
override fun isInitialized(): Boolean {
return tmp != null
}
override val value: T
get() {
// 判断是否初始化,如果未初始化,调用 lambda 创建返回
return if (!isInitialized()) {
block()
} else {
tmp!!
}
}
}
可以注意到,我们定义的委托必须实现 Lazy<T>
接口,并且重写 value
变量。
补充
其实 Kotlin
委托还为我们提供了很多好用的方法,大部分都在 Delegates
类中。
实战可以参考:委托实现双击 back 退出