Kotlin -by 详解
Posted wzgiceman
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin -by 详解相关的知识,希望对你有一定的参考价值。
Kotlin 中 by 关键字用来简化实现代理 (委托) 模式,不仅可以类代理,还可以代理类属性, 监听属性变化,下面我们来介绍by的几种主要使用场景:
- 类的代理 class
- 属性延迟加载 lazy
- 可观察属性 Delegates.observable ( 扩展 Delegates.vetoable )
- 自定义监听属性变化 ReadWriteProperty
- 属性非空强校验 Delegates.notNull()
- Map值 映射到类属性 map
类的代理(代理/委托模式)
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 <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
很明显,代理给一个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)
Map值 映射到类属性
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")
以上是关于Kotlin -by 详解的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin中接口抽象类泛型out(协变)in(逆变)reified关键字的详解
Kotlin中与Java互操作与可空性类型映射属性访问@JvmOverloads@JvmField@JvmStatic@Throws和函数类型操作详解