Kotlin 中的 by lazy
今天讲下Kotlin中的 by lazy (不想看废话的 直接往下拉 直奔主题)
前言
再讲By lazy前 我们先说说 委托模式
**委托模式(delegation pattern)**是软件设计模式中的一项基本技巧。在委托模式中,**有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。**委托模式是一项基本技巧,许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式。委托模式使得我们可以用聚合来替代继承,它还使我们可以模拟mixin。
还是有点模糊? 上例子
class People {
fun sayHello() {
println("Hello World")
}
}
class Student {
fun sayHello() {
People().sayHello()
}
}
fun main() {
Student().sayHello()
}
//Hello World
这就是应该最简单的委托模式了 Main调用Student的sayHello方法时 Student 拥有People的实例 将SayHello委托给People的SayHello的方法去执行。
是不是觉得很简单 觉得自己又行了? 棒棒
正文开始
现在切到Kotlin中的委托 他分为两种 一种是属性委托 一种是类委托
类委托
下面代码的2个问题
- 在Kotlin中 by 是委托的关键字
lazy并不是关键字而表达式 下面会讲到 - 认真的你 发现了为什么
Derived不用实现print方法
通过打印你发现 创建实例
BaseImpl给他传入10并且把这个实例传给了Derived打印结果就对应上了没错 这就是委托 因为
BaseImpl也实现了Base那么Derived就委托BaseImpl执行对应的方法。实际和我们的第一段例子是一样的 只不过Kotlin的语法糖 让我们连方法都省略了 代码变得更简单了interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main() { val b = BaseImpl(10) Derived(b).print() } 10可能你觉得这样并不友好 代码不易阅读了 但你想想 如果
Base的方法如果有好多个 那么你不用by关键字去实现委托 你Derived可就不是一行那么简单了吧 ,那么你现在可能又有一个疑问 我并不是想BaseImpl来委托所有方法的实现 那怎么办捏 其实也很简单 你把不想BaseImpl委托的几个方法 重写下来 就可以了interface Base { fun printMessage() fun printMessageLine() } class BaseImpl(val x: Int) : Base { override fun printMessage() { print(x) } override fun printMessageLine() { println(x) } } class Derived(b: Base) : Base by b { override fun printMessage() { print("abc") } } fun main() { val b = BaseImpl(10) Derived(b).printMessage() Derived(b).printMessageLine() }以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现:
interface Base { val message: String fun print() } class BaseImpl(val x: Int) : Base { override val message = "BaseImpl: x = $x" override fun print() { println(message) } } class Derived(b: Base) : Base by b { // 在 b 的 `print` 实现中不会访问到这个属性 override val message = "Message of Derived" } fun main() { val b = BaseImpl(10) val derived = Derived(b) derived.print() println(derived.message) } //BaseImpl: x = 10 //Message of Derived- 在Kotlin中 by 是委托的关键字
属性委托
属性委托和类委托有一些不一样 他可以利用委托实现属性的延迟加载。如果有JAVA去实现 这并不是一件轻松的事情吧。
你可能有疑问 在Kotlin中 延迟加载不是用
lateinit关键字吗? 是的 但是Kotlin中还有一种延迟加载方式by lazy与lateinit不同的是 在使用lateinit定义的变量前 一定会给他一个实例 保证他不会是空对象 而by lazy则是在第一次使用时 初始化对象 那我们来验证下 什么叫 第一次使用时 初始化对象 。class Bean { val str by lazy { println("Init lazy") "Hello World" } } fun main() { val bean = Bean() println("Init Bean") println(bean.str) println(bean.str) } //Init Bean //Init lazy //Hello World //Hello World通过打印 你应该看得出来了吧 是不是很棒 仅在第一次使用的时候进行初始化。
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性
lazy 文档 创建的新实例懒惰使用指定的初始化函数初始化和默认的线程安全模式
LazyThreadSafetyMode.SYNCHRONIZED。 如果值的初始化抛出一个异常,它会尝试重新初始化在下次访问该值。 注意,返回的实例使用自身同步的。 从外部代码在返回的实例不同步,因为它可能会导致意外的死锁。 另外这个行为可以在未来被改变。看看他的源码 并不是很长
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将
LazyThreadSafetyMode.PUBLICATION作为参数传递给lazy()函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用LazyThreadSafetyMode.NONE模式:它不会有任何线程安全的保证以及相关的开销。val str by lazy(LazyThreadSafetyMode.NONE) { println("Init lazy") "Hello World" }使用要求
对于一个只读属性(即 val 声明的),委托必须提供一个操作符函数
getValue(),该函数具有以下参数:thisRef—— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。property—— 必须是类型KProperty<*>或其超类型。
getValue()必须返回与属性相同的类型(或其子类型)。class Resource class Owner { val valResource: Resource by ResourceDelegate() } class ResourceDelegate { operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource { return Resource() } }对于一个可变属性(即 var 声明的),委托必须额外提供一个操作符函数
setValue(), 该函数具有以下参数:thisRef—— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。property—— 必须是类型KProperty<*>或其超类型。value— 必须与属性类型相同(或者是其超类型)。
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 } } }对于上面代码 当然也算很好理解的
val不可变属性 你只需要实现getValue方法就好 对于var可变属性 既要实现getValue方法也要实现setValue方法。很好理解的吧
总结
通过上面的介绍 你现在对 by lazy 应该有一定了解了吧 可以用 by lazy 实现各种骚操作。
在Android开发中 我们会常常用的 by lazy 放一段 BaseActivity 延迟加载 初始化 DataBinding 和 ViewModel 操作
abstract class BaseActivity : AppCompatActivity() {
protected inline fun <reified T : ViewDataBinding> binding(@LayoutRes contentLayoutId: Int) =
lazy {
DataBindingUtil.setContentView<T>(this, contentLayoutId)
.apply { this.lifecycleOwner = this@BaseActivity }
}
}
class MainActivity :BaseActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding by binding<ActivityMainBinding, MyViewModel>(R.layout.activity_main)
}
}
如果需要 ViewModel 也可以加上去哦 Fragment 也是一样的。