kotlin委托

navysummer navysummer     2022-12-02     681

关键词:

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。


类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。

// 创建接口
interface Base    
    fun print()


// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base 
    override fun print()  print(x) 


// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) 
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10

在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。


属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

属性委托语法格式:

val/var <属性名>: <类型> by <表达式>
  • var/val:属性类型(可变/只读)
  • 属性名:属性名称
  • 类型:属性的数据类型
  • 表达式:委托代理类

by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。

定义一个被委托的类

该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example 
    var p: String by Delegate()


// 委托的类
class Delegate 
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String 
        return "$thisRef, 这里委托了 $property.name 属性"
    

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) 
        println("$thisRef 的 $property.name 属性赋值为 $value")
    

fun main(args: Array<String>) 
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)

输出结果为:

[email protected], 这里委托了 p 属性
[email protected]  p 属性赋值为 Runoob
[email protected], 这里委托了 p 属性

标准委托

Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。

延迟属性 Lazy

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy 
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"


fun main(args: Array<String>) 
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值

执行输出结果:

computed!
Hello
Hello

可观察属性 Observable

observable 可以用于实现观察者模式。

Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。

在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates

class User 
    var name: String by Delegates.observable("初始值") 
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    


fun main(args: Array<String>) 
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"

执行输出结果:

旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值

把属性储存在映射中

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class Site(val map: Map<String, Any?>) 
    val name: String by map
    val url: String  by map


fun main(args: Array<String>) 
    // 构造函数接受一个映射参数
    val site = Site(mapOf(
        "name" to "菜鸟教程",
        "url"  to "www.runoob.com"
    ))
    
    // 读取映射值
    println(site.name)
    println(site.url)

执行输出结果:

菜鸟教程
www.runoob.com

如果使用 var 属性,需要把 Map 换成 MutableMap:

class Site(val map: MutableMap<String, Any?>) 
    val name: String by map
    val url: String by map


fun main(args: Array<String>) 

    var map:MutableMap<String, Any?> = mutableMapOf(
            "name" to "菜鸟教程",
            "url" to "www.runoob.com"
    )

    val site = Site(map)

    println(site.name)
    println(site.url)

    println("--------------")
    map.put("name", "Google")
    map.put("url", "www.google.com")

    println(site.name)
    println(site.url)

执行输出结果:

菜鸟教程
www.runoob.com
--------------
Google
www.google.com

Not Null

notNull 适用于那些无法在初始化阶段就确定属性值的场合。

class Foo 
    var notNullBar: String by Delegates.notNull<String>()


foo.notNullBar = "bar"
println(foo.notNullBar)

需要注意,如果属性在赋值前就被访问的话则会抛出异常。


局部委托属性

你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:

fun example(computeFoo: () -> Foo) 
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) 
        memoizedFoo.doSomething()
    

memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。


属性委托要求

对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:

  • thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型

这个函数必须返回与属性相同的类型(或其子类型)。

对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:

  • property —— 必须是类型 KProperty<*> 或其超类型
  • new value —— 必须和属性同类型或者是它的超类型。

翻译规则

在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C 
    var prop: Type by MyDelegate()


// 这段是由编译器生成的相应代码:
class C 
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。


提供委托

通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例。

provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。

例如,如果要在绑定之前检查属性名称,可以这样写:

class ResourceLoader<T>(id: ResourceID<T>) 
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> 
        checkProperty(thisRef, prop.name)
        // 创建委托
    

    private fun checkProperty(thisRef: MyUI, name: String)  …… 


fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T>  …… 

class MyUI 
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)

provideDelegate 的参数与 getValue 相同:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型。

在创建 MyUI 实例期间,为每个属性调用 provideDelegate 方法,并立即执行必要的验证。

如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名,这不是很方便:

// 检查属性名称而不使用“provideDelegate”功能
class MyUI 
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")


fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> 
   checkProperty(this, propertyName)
   // 创建委托

在生成的代码中,会调用 provideDelegate 方法来初始化辅助的 prop$delegate 属性。 比较对于属性声明 val prop: Type by MyDelegate() 生成的代码与 上面(当 provideDelegate 方法不存在时)生成的代码:

class C 
    var prop: Type by MyDelegate()


// 这段代码是当“provideDelegate”功能可用时
// 由编译器生成的代码:
class C 
    // 调用“provideDelegate”来创建额外的“delegate”属性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)

请注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。

kotlin委托

...明白 两点核心:1.委托:就是A的活交给B来做2.函数在kotlin中就是普通类型,它的声明形式:(x:type,...)->type 这里给出demo,局部委托属性局部委托属性:就是可以将局部变量当作属性来对待,于是就可以对这个局部属性... 查看详情

深入kotlin-委托(代码片段)

委托委托不是java中的概念。它仅仅是一种设计模式,但是kotlin从语言层级上,通过by关键字提供了对委托模式的支持。类委托interfaceMyInteface//1 funmyPrint()classMyInterfaceImpl(valname:String):MyInterface//2 overridefunmyPring() println(name) 查看详情

kotlin委托(代码片段)

1、类委托类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interfaceIDemofunaction1()funaction2()//被委托的类classDemoImpl:IDemooverridefunaction1()println("doaction1")overridefunaction2()println("doaction2")/ 查看详情

kotlin委托(代码片段)

1、类委托类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interfaceIDemofunaction1()funaction2()//被委托的类classDemoImpl:IDemooverridefunaction1()println("doaction1")overridefunaction2()println("doaction2")/ 查看详情

kotlin委托(代码片段)

1、类委托类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interfaceIDemofunaction1()funaction2()//被委托的类classDemoImpl:IDemooverridefunaction1()println("doaction1")overridefunaction2()println("doaction2")/ 查看详情

kotlin委托的本质以及mmkv的应用(代码片段)

作者:DylanCai前言很多人写Kotlin只用到一些Java已有的东西,单纯地把Java代码翻译成Kotlin代码,而Kotlin一些好用的语法糖都没有用过。本文给大家介绍一个Java不常见但是很好用的Kotlin语法糖——Kotlin委托。Kotlin委托包... 查看详情

kotlin学习之委托机制(代码片段)

类的委托    委托模式可以很好的替代实现继承,kotlin本身支持需要零样板代码,一个类Derived可以继承Base并委托它所有的public方法到一个指定的类:interfaceBasefunprint()classBaseImpl(valx:Int):Baseoverridefunprint()print(x)classDerived(b:B... 查看详情

kotlin委托工厂map存储属性值(代码片段)

1.委托工厂(1)概念?委托工厂顾名思义:生产委托对象的工厂类。(2)定义?该类实现了operator修饰的provideDelegate方法,返回ReadWriteProperty/ReadOnlyProperty,该类就可提供对应类型的委托对象。/***委托工厂类(生产委托对象)*/classP... 查看详情

kotlin中委托的概念和原理(代码片段)

kotlin中委托的概念和原理问题背景kotlin的日常使用过程中,经常会使用到委托机制,问题来了,委托机制究竟是什么呢?委托模式:多个对象接收并处理同一请求,他们将请求委托给另一个对象统一处理请求。比如调用A类的metho... 查看详情

kotlin委托必须重视的几个点(代码片段)

委托模式是实现继承的一个很好的替代方式,也是Kotlin语言的一种特性,可以很优雅的实现委托模式。在开发过程中也少不了使用它,但常常都会被低估。所以今天就让它得到重视,去充分的掌握kotlin委托特性以... 查看详情

kotlin实战之委托总结(代码片段)

...是觉得将自己的云笔记分享出来吧~特别说明,kotlin系列文章均以Java差异为核心进行提炼,与Java相同部分不再列出。随着kotlin官方版本的迭代,文中有些语法可能会发生变化,请务必留意,语言领悟精髓... 查看详情

kotlin学习记录(代码片段)

关于kotlin中的委托:1.属性委托  我们知道kotlin为我们提供了几种标准的委托,如下所示  延迟属性(lazyproperties):其值只在?次访问时计算;  可观察属性(observableproperties):监听器会收到有关此属性变更的通知;  把... 查看详情

kotlin小知识之泛型和委托(代码片段)

...属性实现一个自己的lazy函数泛型和委托泛型的基本用法Kotlin当中的泛型机制和Java当中的泛型机制还是有异同的所谓泛型就是说在一般的编程模式下面,我们需要给一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型... 查看详情

firebase db:具有 Kotlin 委托属性的模型

】firebasedb:具有Kotlin委托属性的模型【英文标题】:firebasedb:modelswithKotlindelegatedproperties【发布时间】:2017-08-0122:14:13【问题描述】:我正在使用Kotlin对象来处理我的Firebase数据库模型,如intheguide所述。我有很多存储为字符串的... 查看详情

如何为 kotlin 委托属性使用不同的类型

】如何为kotlin委托属性使用不同的类型【英文标题】:Howtousedifferenttypesforkotlindelegationproperties【发布时间】:2021-10-1812:18:19【问题描述】:我尝试编写一个LiveData类,它将原始(可为空的)值包装到一个Result类中,清楚地表明是... 查看详情

kotlin委托必须重视的几个点(代码片段)

委托模式是实现继承的一个很好的替代方式,也是Kotlin语言的一种特性,可以很优雅的实现委托模式。在开发过程中也少不了使用它,但常常都会被低估。所以今天就让它得到重视,去充分的掌握kotlin委托特性以... 查看详情

kotlin学习与实践数据类类委托“object”

...向外提供getter、setter、toString、Hashcode等机械式的方法。*Kotlin也像Java一样提供了toStringequalshashCode方法,也可以重写他们classClient1(valname:String,valpostalCode:Int){overridefuntoStri 查看详情

将 Kotlin 属性委托与 by 一起使用时出现 NullPointerException (NPE)

】将Kotlin属性委托与by一起使用时出现NullPointerException(NPE)【英文标题】:NullPointerException(NPE)whenusingKotlinpropertydelegatewithby【发布时间】:2020-11-1707:28:59【问题描述】:我有一个类,它在文本字段中接受用户输入并使用提供的函数... 查看详情