我们都知道委托模式也叫代理模式,指的是一个对象接收到请求之后将请求转交由另外的对象来处理。
Kotlin直接支持委托模式,kotlin通过关键字by实现委托,更加优雅,简洁。
我们在使用Kotlin开发的时候,经常会使用by来实现一些操作,使用by lazy来实现懒加载。
虽然日常用的比较多,但却不明白其原理,lazy又代表着什么 ?
还有看似普通的by,实则可以实现很多的功能,可以更方便我们的开发。
接下来,我们来陆续回答这些问题。
在Kotlin中,委托分为类委托和属性委托
类委托的核心思想是将一个类的一些具体实现委托给另一个类去完成。
我们新建一个接口Base,声明方法print()
interface Base {
fun print()
}
然后创建一个实现类Impl1
class Impl1() : Base {
override fun print1() {
Log.i("TAG", "------打印了print1------")
}
override fun print2() {
Log.i("TAG", "------打印了print2------")
}
}
接着,创建一个实现类Impl2,其实现会委托给Impl1
同时,重写了print1方法,这意味着print1会由Impl2自己来实现
class Impl2(base: Base) : Base by base {
override fun print1() {
Log.i("TAG", "------重写了print1------")
}
}
运行程序,可以发现打印如下内容
TAG: ------重写了print1------
TAG: ------打印了print2------
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其委托给一个代理类,从而实现对该类的属性统一管理。
首先,我们需要先定义一个被委托类,实现setValue / getValue方法,这是使用by的一个约定,否则是无法使用by的。
注意 : 如果是只读的(val)只需实现getValue(),如果读写都需要(var),setValue / getValue都需要实现
class Delegate {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("$thisRef, 这里委托了 ${property.name} 属性")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
this.value = value
}
}
需要注意的是setValue / getValue方法的几个参数
KProperty<*>或其超类型接着定义包含属性委托的类
class Example {
var p: String by Delegate()
}
进行调用
fun main() {
val example = Example()
println("第一次打印:" + example.p) // 访问该属性,调用 getValue() 函数
example.p = "Heiko" // 调用 setValue() 函数
println("第二次打印:" + example.p)
}
可以看到打印的日志如下
I/System.out: com.heiko.koltintest.Test2$Example@9c2d655, 这里委托了 p 属性
I/System.out: 第一次打印:
I/System.out: com.heiko.koltintest.Test2$Example@9c2d655 的 p 属性赋值为 Heiko
I/System.out: com.heiko.koltintest.Test2$Example@9c2d655, 这里委托了 p 属性
I/System.out: 第二次打印:Heiko
class Example {
var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("$thisRef, 这里委托了 ${property.name} 属性")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
this.value = value
}
}
fun main() {
var e by Example()
println(e) // 访问该属性,调用 getValue() 函数
e = "Heiko" // 调用 setValue() 函数
println(e)
}
打印日志如下
I/System.out: null, 这里委托了 e 属性
I/System.out: null 的 e 属性赋值为 Heiko
I/System.out: null, 这里委托了 e 属性
I/System.out: Heiko
class Example {
var value: String = ""
}
inline operator fun Example.getValue(thisRef: Any?, property: KProperty<*>): String {
println("$thisRef, 这里委托了 ${property.name} 属性")
return value
}
inline operator fun Example.setValue(
thisRef: Any?,
property: KProperty<*>,
value: String
) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
this.value = value
}
fun main() {
var example by Example()
println(example) // 访问该属性,调用 getValue() 函数
example = "Heiko" // 调用 setValue() 函数
println(example)
}
打印日志如下
I/System.out: null, 这里委托了 example 属性
I/System.out: null 的 example 属性赋值为 Heiko
I/System.out: null, 这里委托了 example 属性
I/System.out: Heiko
实现属性委托,需要实现getValue/setValue方法,这相对麻烦,也容易写错,为此,kotlin为我们提供了operator方法的 ReadOnlyProperty/ReadWriteProperty 接口。
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
val属性实现这个var属性实现这个我们来修改下代码
class Example : ReadWriteProperty<Any?, String> {
var value: String = ""
override operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("$thisRef, 这里委托了 ${property.name} 属性")
return value
}
override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
this.value = value
}
}
fun main() {
var e by Example()
println(e) // 访问该属性,调用 getValue() 函数
e = "Heiko" // 调用 setValue() 函数
println(e)
}
可以发现运行也是一样的
lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
lazy还可以传入参数 (如果不传入参数,那么使用的是LazyThreadSafetyMode.SYNCHRONIZED)
public enum class LazyThreadSafetyMode {
SYNCHRONIZED,
PUBLICATION,
NONE,
}
Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("" ) {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
vetoable 与 observable一样,可以观察属性值的变化,不同的是,vetoable可以控制返回值来决定属性是否生效
一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
在这个例子中,构造函数接受一个映射参数
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
委托属性会从这个映射中取值(通过字符串键——属性的名称)
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
这也适用于 var 属性,需要把只读的 Map 换成 MutableMap
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
从 Kotlin 1.4 开始,一个属性可以把它的 getter 与 setter 委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。该委托属性可以为
为将一个属性委托给另一个属性,应在委托名称中使用恰当的 :: 限定符,例如,this::delegate 或 MyClass::delegate。
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
var delegatedToMember: Int by this::memberInt
var delegatedToTopLevel: Int by ::topLevelInt
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt
这是很有用的,例如,当想要以一种向后兼容的方式重命名一个属性的时候:引入一个新的属性、 使用 @Deprecated 注解来注解旧的属性、并委托其实现。
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
fun main() {
val myClass = MyClass()
// 通知:'oldName: Int' is deprecated.
// Use 'newName' instead
myClass.oldName = 42
println(myClass.newName) // 42
}
notNull适用于那些无法在初始化阶段就确定属性值的场合
class Foo{
var notNullBar:String by Delegates.notNull<String>()
}
foo.notNullBar="bar"
println(foo.notNullBar)
需要注意,如果属性在赋值前就被访问的话则会抛出异常
除了以上的功能,还有很多地方可以用到kotlin委托,比如我们可以用来封装SharedPreferences,View属性获取/设置的委托,当然,还能和ViewBinding结合使用,使ViewBinding的声明更加便捷。
以前我们使用ViewBinding,需要如下定义
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private val binding : ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
我们可以使用Kotlin委托去实现ViewBinding的inflate过程
class ActivityViewBindings<in A : ComponentActivity, out T : ViewBinding>(private val viewBinder: (A) -> T) :
ReadOnlyProperty<A, T> {
override fun getValue(thisRef: A, property: KProperty<*>): T {
return viewBinder(thisRef)
}
}
public inline fun <A : ComponentActivity, T : ViewBinding> ComponentActivity.viewBinding(
crossinline vbFactory: (View) -> T,
crossinline viewProvider: (A) -> View = ::findRootView
): ActivityViewBindings<A, T> {
return ActivityViewBindings { activity -> vbFactory(viewProvider(activity)) }
}
fun findRootView(activity: Activity): View {
val contentView = activity.findViewById<ViewGroup>(android.R.id.content)
checkNotNull(contentView) { "Activity has no content view" }
return when (contentView.childCount) {
1 -> contentView.getChildAt(0)
0 -> error("Content view has no children. Provide a root view explicitly")
else -> error("More than one child view found in the Activity content view")
}
}
然后进行使用,就可以省略ActivityMainBinding.inflate()这一步了
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private val binding : ActivityMainBinding by viewBinding(ActivityMainBinding::bind)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
当然,这只是最粗略的写法,还有其他更简单的写法,具体详见我的另一篇博客 ViewBinding与Kotlin委托结合使用,去除setContentView,其实现原理解析
我们来写个伪代码,来实现一个自己的by lazy,实现的效果如下
private val hello : String by myLazy {
"hello"
}
首先,我们定义一个接口叫做MyLazy
public interface MyLazy<out T> {
public val value: T
}
然后,按照Kotlin委托的规则,我们需要实现getValue()
public inline operator fun <T> MyLazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
然后,定义MyLazy接口的实现类SynchronizedMyLazyImpl
internal object UNINITIALIZED_VALUE
private class SynchronizedMyLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : MyLazy<T> {
private var initializer: (() -> T)? = initializer
//用来保存值,如果已经被初始化,就不是默认值了
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
private val lock = lock ?: this
override val value: T
get() { //其实就是一个双重校验锁实现的单例
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
//当value已初始化,直接返回其值
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
//通过加锁的方式,再次进行判断value是否已初始化
if (_v2 !== UNINITIALIZED_VALUE) {
//当value已初始化,直接返回其值
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
//当value未初始化,调用 initializer!!() 进行初始化
val typedValue = initializer!!()
//赋值 _value
_value = typedValue
initializer = null
typedValue
}
}
}
}
定义函数myLazy,参数需要传入initializer: () -> T
public fun <T> myLazy(initializer: () -> T): MyLazy<T> = SynchronizedMyLazyImpl(initializer)
然后进行使用就可以了
private val hello : String by myLazy {
"hello"
}
推荐我的另一篇文章 :
ViewBinding与Kotlin委托结合使用,去除setContentView,其实现原理解析
参考
一文彻底搞懂Kotlin中的委托
委托 - Kotlin 语言中文站
Kotlin by 关键字
Kotlin常用的by lazy你真的了解吗
Kotlin原理-by关键字
kotlin by 关键字用法及使用场景