Kotlin 中 by 关键字用来简化实现代理 (委托) 模式,不仅可以类代理,还可以代理类属性, 监听属性变化,下面我们来介绍by的几种主要使用场景:
class ByTest {
// 定义一个接口,和一个方法 show()
interface Base {
fun show()
}
// 定义类实现 Base 接口, 并实现 show 方法
open class BaseImpl : Base {
override fun show() {
AbLogUtil.e("BaseImpl::show()")
}
}
// 定义代理类实现 Base 接口, 构造函数参数是一个 Base 对象
// by 后跟 Base 对象, 不需要再实现 show()
class BaseProxy(base: Base) : Base by base {
fun showOther() {
AbLogUtil.e("BaseImpl::showOther()")
}
}
// main 方法
fun mainGo() {
val base = BaseImpl()
BaseProxy(base).show()
BaseProxy(base).showOther()
}
}
输出结果
BaseImpl::show()
BaseImpl::showOther()
转成 Java 代码
public interface Base {
void show();
}
// BaseImpl.java
public class BaseImpl implements Base {
public void show() {
String var1 = "BaseImpl::show()";
System.out.print(var1);
}
}
// BaseProxy.java
public final class BaseProxy implements Base {
// $FF: synthetic field
private final Base $$delegate_0;
public BaseProxy(@NotNull Base base) {
Intrinsics.checkParameterIsNotNull(base, "base");
super();
this.$$delegate_0 = base;
}
public void show() {
this.$$delegate_0.show();
}
}
// NormalKt.java
public final class NormalKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
BaseImpl base = new BaseImpl();
(new BaseProxy((Base)base)).show();
}
}
和一般的代理模式是不是一样?不过 by 关键字节省了不少代码
可以理解成按需加载,使用时生成一个类单例
对象,不使用无须生成对象
private val user: User by lazy { ShareSparse.getValueBy(ShareSparse.USER_CLS) as User }
private lateinit var sex: String
lzay 后跟表达式,表达式返回值必须和属性类型一致
注意 lateinit修饰的对象在赋值前调用会抛出异常
可观察属性,本质就是观察者模式,在Java中也可以实现这个设计模式,但Kotlin实现观察者模式不需要样板代码。在谈Kotlin的可观察属性前,先看下Kotlin里面的委托。同样的,委托也是一种设计模式,它的结构如下图所示:
Kotlin通过 Delegates.observable()实现可观察属性:
// main 方法
fun mainGo() {
name = "dddd"
name = "hhhh"
}
// 观察属性
var name: String by Delegates.observable("hello", { kProperty: KProperty<*>, oldName: String, newName: String ->
AbLogUtil.e("${kProperty.name}---${oldName}--${newName}")
})
输出
name---hello--dddd
name---dddd--hhhh
可观察属性有什么用处呢?ListView中有一个经典的Crash:在数据长度与Adapter中的Cell的长度不一致时,会报IllegalStateException异常。这个异常的根本原因是修改了数据之后,没有调用notifyDataSetChanged,导致ListView没有及时刷新。如果我们把数据做成可观察属性,在观察回调方法中直接刷新ListView,可以杜绝这个问题;其实很多简单的接口回调处理也可以使用
源码解析
// observable方法
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
// vetoable 方法
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}
public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
private var value = initialValue
/**
* The callback which is called before a change to the property value is attempted.
* The value of the property hasn't been changed yet, when this callback is invoked.
* If the callback returns `true` the value of the property is being set to the new value,
* and if the callback returns `false` the new value is discarded and the property remains its old value.
*/
protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true
/**
* The callback which is called after the change of the property is made. The value of the property
* has already been changed when this callback is invoked.
*/
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) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
}
class User {
var name: String by Delegates.notNull()
fun init(name: String) {
this.name = name
}
}
fun main(args: Array<String>) {
val user = User()
// print(user.name)
// user.name -> IllegalStateException
user.init("Carl")
println(user.name)
}
源码解析
// notNull 方法
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
// 具体实现方法 NotNullVar()
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
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
}
}
常用的以上by lazy 延迟委托 , by Delegates.observable() 可观察属性委托, by Delegates.nonNull()等等
语法是: val/var<属性名>:<类型> by<表达式>。在 by后面的表达式是该委托
属性对应的get() (和set())会被委托给它的getValue()和setValue()方法。
所以,kotlin中的代理仅仅是代理了get 和 set 两个方法
属性的委托不必实现任何的接口,但是需要重载操作符getValue()函数(和setValue()——对于var属性)但是对于val可以实现ReadOnlyProperty,var实现ReadWriteProperty接口(就是帮你实现两个需要重载的操作符的接口)来更快的进行自定义委托.
那么他们究竟是怎么工作的呢?
我们来看看nonNull()委托的代码吧
我初始化了一个Int对象,且用委托表示int1不可以为空
var int1 : Int by Delegates.notNull()
那么我们进入notNull()的代码里查看他的实现
public fun
很明显,代理给一个ReadWriteProperty类了
那么NotNullVar()是什么呢
// notNull 方法
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
// 具体实现方法 NotNullVar()
private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
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
}
}
解释一下,当给int1复制的时候,就会自动调用上面的setValue(),其中的三个参数
第一个thisRef表示持有该对象的对象,即该int1的所有者.
第二个参数 property 是该值的类型,
第三个参数 value 就是属性的值了
在第一次调用setvalue的时候,将该值存到value中,getvalue的时候对value进行是否为空的判断.空就抛异常,这就完成了nonNull的委托
然后实现一个自定义的委托属性
//自定义委托属性
class A() {
// 运算符重载
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
println("$value has been assigned to ${prop.name} in $thisRef")
}
}
// main 方法
fun mainGo() {
var name :String by A()
name="aaaa"
AbLogUtil.e(name)
}
输出
aaaa has been assigned to name in null
null, thank you for delegating 'name' to me!
比较实用可以方便实现弱引用对象处理,防止移动端内存泄露
/**
*
*Describe:弱引用封装类 -kotlin
*
*Created by zhigang wei
*on 2018/5/4
*
*Company :cpx
*/
class Weak<T : Any>(initializer: () -> T?) {
var weakReference = WeakReference<T?>(initializer())
constructor() : this({
null
})
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return weakReference.get()
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
weakReference = WeakReference(value)
}
}
class UserX(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = UserX(mapOf(
"name" to "John Doe",
"age" to 123
))
// key 不存在报错 Key age is missing in the map.
// 类型不一致 java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
println("name = ${user.name}, age = ${user.age}")
}