26:kotlin 类和对象 -- 委托属性(Delegation properties )

2023-12-07 13:15

本文主要是介绍26:kotlin 类和对象 -- 委托属性(Delegation properties ),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

尽管每次需要时都可以手动实现一些常见类型的属性,但将它们实现一次、添加到库中以便以后重用会更为方便

  • 懒加载属性:仅在首次访问时计算值。
  • 可观察属性:监听器会收到有关此属性更改的通知。
  • 将属性存储在映射中而不是为每个属性单独创建字段。

为了涵盖这些(以及其他)情况,kotlin支持委托属性(delegated properties)

语法如下:val/var <property name>: <Type> by <expression>。在 by 之后的表达式是一个委托,因为与属性对应的 get()(和 set())将被委托给其 getValue()setValue() 方法。属性委托不必实现一个接口,但它们必须提供一个 getValue() 函数(对于 var 还需要提供 setValue()

class Delegate {operator fun getValue(thisRef: Any?, property: KProperty<*>): String {return "$thisRef, thank you for delegating '${property.name}' to me!"}operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {println("$value has been assigned to '${property.name}' in $thisRef.")}
}class Example {var p: String by Delegate()	// 委托
}fun main() {Example().p = "王老吉" // 王老吉 has been assigned to 'p' in com.example.Example@6acdbdf5.println(Example().p)    // com.example.Example@7a1ebcd8, thank you for delegating 'p' to me!
}

当你从委托给Delegate的实例中p中读取属性时,将调用DelegategetValue()函数。第一个参数是你从中读取p的对象,而第二个参数保存了p属性本身的描述

你可以在函数或代码块内声明一个委托属性;它不必是类的成员

标准委托

kotlin为重用代理定义了模板方法

懒加载属性

lazy() 是一个接受 lambda并返回 Lazy<T> 实例的函数,可用作实现懒加载属性的委托。第一调用 get() ,会执行lambda表达式并记住结果,之后的调用直接返回结果

val lazyValue: String by lazy {println("computed!")"Hello"
}fun main() {println(lazyValue)println(lazyValue)
//    computed!
//    Hello
//    Hello}

默认情况下,属性懒懒加载是同步的,在一个线程中进行,其他线程看到相同的结果。如果允许多个线程同时初始化,使用LazyThreadSafetyMode.PUBLICATION参数。

val lazyValue: String by lazy (LazyThreadSafetyMode.PUBLICATION){println("computed!")"Hello"
}

如果确信属性初始化和使用是在同一个线程中进行的,可以使用LazyThreadSafetyMode.NONE参数,但是不保证线程安全

可观察属性

Delegates.observable() 接受两个参数:初始值和用于处理修改的处理程序。

每次你对属性进行赋值时(在赋值完成后),处理程序都会被调用。它有三个参数:被赋值的属性、旧值和新值

class User {var name: String by Delegates.observable("<no name>") {prop, old, new ->  println("$old -> $new")}
}fun main() {val user = User()user.name = "first" // <no name> -> firstuser.name = "second"    // first -> second
}

如果你想拦截赋值并否决它们,使用 vetoable() 而不是 observable()。传递给 vetoable() 的处理程序将在分配新属性值之前被调用

class User {var name: String by Delegates.vetoable("<no name>") {prop, old, new ->  println("$old -> $new")true}
}fun main() {val user = User()user.name = "first" // <no name> -> firstuser.name = "second"    // first -> second
}

委托给另一个属性

一个属性可以将其 gettersetter 委托给另一个属性。这样的委托对于顶层和类属性(成员和扩展)都是可用的

委托属性可:

  • 顶层属性
  • 同一类的成员或扩展属性
  • 另一个类的成员或扩展属性
var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {var delegatedToMember: Int by this::memberIntvar delegatedToTopLevel: Int by ::topLevelIntval 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' insteadmyClass.oldName = 42println(myClass.newName) // 42
}

在Map中存储属性

一个常见的用例是将属性的值存储在Map中。这在解析JSON或执行其他动态任务的应用程序中经常发生。在这种情况下,您可以使用Map实例本身作为委托属性的委托

class User(val map: Map<String, Any?>) {val name: String by mapval age: Int     by map
}fun main() {// 构造方法接收一个Mapval user = User(mapOf("name" to "John Doe","age"  to 25))
}

可变的Map

class MutableUser(val map: MutableMap<String, Any?>) {var name: String by mapvar age: Int     by map
}

局部委托变量

可以将局部变量声明为委托属性。例如,可以使局部变量成为延迟加载的

fun example(computeFoo: () -> Foo) {val memoizedFoo by lazy(computeFoo)if (someCondition && memoizedFoo.isValid()) {memoizedFoo.doSomething()}
}

The memoizedFoo variable will be computed on first access only. If someCondition fails, the variable won’t be computed at all.

属性委托的要求

对于只读属性val,委托应提供一个带有以下参数的getValue()

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

getValue()必须返回与属性相同的类型(或其子类型)

import kotlin.reflect.KPropertyclass Resourceclass Owner {val valResource: Resource by ResourceDelegate()
}class ResourceDelegate {operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {return Resource()}
}

对于可变属性var,委托还必须提供一个带有以下参数的setValue()

  • thisRef必须与属性所有者的类型相同,或者是其超类型(对于扩展属性,它应该是被扩展的类型)
  • property必须是KProperty<*>类型或其超类型
  • value必须是与属性相同的类型(或其超类型)
import kotlin.reflect.KPropertyclass Resourceclass 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}}
}

getValue()/setValue()函数可以作为委托类的成员函数或扩展函数提供(当委托类没有这两个函数时可以定义扩展函数)。这两个函数都需要用operator关键字标记。

可以通过使用kotlin标准库中的ReadOnlyPropertyReadWriteProperty接口,以匿名对象的方式创建委托,而无需创建新的类。它们提供了所需的方法:getValue()ReadOnlyProperty中声明;ReadWriteProperty继承了它并添加了setValue()。这意味着您可以在期望ReadOnlyProperty的任何地方传递一个ReadWriteProperty

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KPropertyclass Resourcefun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty<Any?, Resource> =object : ReadWriteProperty<Any?, Resource> {var curValue = resourceoverride fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValueoverride fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {curValue = value}}val readOnlyResource: Resource by resourceDelegate()
var readWriteResource: Resource by resourceDelegate()fun main() {readWriteResource = Resource()println(readOnlyResource)
}

委托属性转换规则

在底层,kotlin编译器为某些类型的委托属性生成辅助属性,然后将委托交给这些辅助属性

出于优化目的,编译器在一些情况下不会生成辅助属性。通过委托到另一个属性的示例学习有关优化的信息。

例如,对于属性prop,它生成隐藏的属性prop$delegate,而访问器的代码简单地委托给这个附加属性

class C {var prop: Type by MyDelegate()
}// 上边的编码被编译为一下内容
class C {private val prop$delegate = MyDelegate()var prop: Typeget() = prop$delegate.getValue(this, this::prop)set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

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

优化委托属性的情况

如果委托是以下情况之一,将省略 $delegate 字段

  • 引用属性
class C<Type> {private var impl: Type = ...var prop: Type by ::impl
}
  • 命名对象
object NamedObject {operator fun getValue(thisRef: Any?, property: KProperty<*>): String = ...
}val s: String by NamedObject
  • 同一模块中,使用backing field 和默认getter()final val属性
val impl: ReadOnlyProperty<Any?, String> = ...class A {val s: String by impl
}
  • 常量表达式、枚举项、thisnullthis示例
class A {operator fun getValue(thisRef: Any?, property: KProperty<*>) ...val s by this
}

委托给另一个属性时的转换规则

当委托给另一个属性时,编译器生成对所引用属性的直接访问。这意味着编译器不会生成字段 prop$delegate。这种优化有助于节省内存

class C<Type> {private var impl: Type = ...var prop: Type by ::impl
}

prop 变量的属性访问器直接调用 impl 变量,跳过了委托属性的 getValuesetValue 运算符,因此不需要 KProperty 引用对象。

对于上述代码,编译器生成了以下代码

class C<Type> {private var impl: Type = ...var prop: Typeget() = implset(value) {impl = value}fun getProp$delegate(): Type = impl // This method is needed only for reflection
}

提供委托

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

provideDelegate 的一种可能用例是在属性初始化时检查其一致性

例如,要在绑定之前检查属性名称,您可以编写如下代码

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}class ResourceLoader<T>(id: ResourceID<T>) {operator fun provideDelegate(thisRef: MyUI,prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {checkProperty(thisRef, prop.name)// create delegatereturn ResourceDelegate()}private fun checkProperty(thisRef: MyUI, name: String) { ... }
}class MyUI {fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }val image by bindResource(ResourceID.image_id)val text by bindResource(ResourceID.text_id)
}

provideDelegate 的参数与 getValue 相同:

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

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

如果没有拦截属性与其委托之间的绑定的能力,要实现相同的功能,您将不得不显式传递属性名称,这不是很方便

// Checking the property name without "provideDelegate" functionality
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)// create delegate
}

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

class C {var prop: Type by MyDelegate()
}// this code is generated by the compiler
// when the 'provideDelegate' function is available:
class C {// calling "provideDelegate" to create the additional "delegate" propertyprivate val prop$delegate = MyDelegate().provideDelegate(this, this::prop)var prop: Typeget() = prop$delegate.getValue(this, this::prop)set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

请注意,provideDelegate 方法仅影响辅助属性的创建,不影响为 gettersetter 生成的代码

使用标准库中的 PropertyDelegateProvider 接口,可以创建委托提供程序而无需创建新的类

val provider = PropertyDelegateProvider { thisRef: Any?, property ->ReadOnlyProperty<Any?, Int> {_, property -> 42 }
}
val delegate: Int by provider

这篇关于26:kotlin 类和对象 -- 委托属性(Delegation properties )的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/466014

相关文章

苹果macOS 26 Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色

《苹果macOS26Tahoe主题功能大升级:可定制图标/高亮文本/文件夹颜色》在整体系统设计方面,macOS26采用了全新的玻璃质感视觉风格,应用于Dock栏、应用图标以及桌面小部件等多个界面... 科技媒体 MACRumors 昨日(6 月 13 日)发布博文,报道称在 macOS 26 Tahoe 中

CSS3中的字体及相关属性详解

《CSS3中的字体及相关属性详解》:本文主要介绍了CSS3中的字体及相关属性,详细内容请阅读本文,希望能对你有所帮助... 字体网页字体的三个来源:用户机器上安装的字体,放心使用。保存在第三方网站上的字体,例如Typekit和Google,可以link标签链接到你的页面上。保存在你自己Web服务器上的字

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

SpringBoot读取ZooKeeper(ZK)属性的方法实现

《SpringBoot读取ZooKeeper(ZK)属性的方法实现》本文主要介绍了SpringBoot读取ZooKeeper(ZK)属性的方法实现,强调使用@ConfigurationProperti... 目录1. 在配置文件中定义 ZK 属性application.propertiesapplicati

Java反射实现多属性去重与分组功能

《Java反射实现多属性去重与分组功能》在Java开发中,​​List是一种非常常用的数据结构,通常我们会遇到这样的问题:如何处理​​List​​​中的相同字段?无论是去重还是分组,合理的操作可以提高... 目录一、开发环境与基础组件准备1.环境配置:2. 代码结构说明:二、基础反射工具:BeanUtils

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Spring中管理bean对象的方式(专业级说明)

《Spring中管理bean对象的方式(专业级说明)》在Spring框架中,Bean的管理是核心功能,主要通过IoC(控制反转)容器实现,下面给大家介绍Spring中管理bean对象的方式,感兴趣的朋... 目录1.Bean的声明与注册1.1 基于XML配置1.2 基于注解(主流方式)1.3 基于Java

C++/类与对象/默认成员函数@构造函数的用法

《C++/类与对象/默认成员函数@构造函数的用法》:本文主要介绍C++/类与对象/默认成员函数@构造函数的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录名词概念默认成员函数构造函数概念函数特征显示构造函数隐式构造函数总结名词概念默认构造函数:不用传参就可以

C++类和对象之默认成员函数的使用解读

《C++类和对象之默认成员函数的使用解读》:本文主要介绍C++类和对象之默认成员函数的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、默认成员函数有哪些二、各默认成员函数详解默认构造函数析构函数拷贝构造函数拷贝赋值运算符三、默认成员函数的注意事项总结一