kotlin类与对象——>委托、委托属性
1.委托
1.1 委托的实现
委托模式已经证明是实现继承的一个很好的替代方式,而 Kotlin 可以零样板代码地原生支持它。 Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base :
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}
// by-子句表示 b 将会在 Derived 中内部存储,并且编译器将生成转发给 b 的所有 Base 的方法。
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived (b).print()
}
1.2 覆盖由委托实现的接口成员
覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。如果将 override fun printMessage() { print("abc") } 添加到 Derived,那么当调用 printMessage 时程序 会输出“abc”而不是“10”:
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)
}
注意:在 JVM 平台:当使用带有 default 方法的接口(包括带有 @JvmDefault 注解的 Kotlin 接口)进行委托时,即使实际的委托类型提供了其自身的实现也会调用默认实现。详细信息请参 ⻅在 Java 中调用 Kotlin。
2.委托属性
有一些常⻅的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是如果能够为大家把他们只 实现一次并放入一个库会更好。例如包括:
— 延迟属性(lazyproperties):其值只在首次访问时计算;
— 可观察属性(observableproperties):监听器会收到有关此属性变更的通知;
— 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
为了涵盖这些(以及其他)情况,Kotlin 支持委托属性:
class Example {
var p: String by Delegate()
}
语法是:val/var <属性名>: <类型> by <表达式>。在by后面的表达式是该委托,因为属性对应 的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。属性的委托不必实现 任何的接口,但是需要提供一个 getValue() 函数(与 setValue() ⸺对于 var 属性)
例如:
import kotlin.reflect.KProperty
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.")
}
}
当我们从委托到一个 Delegate 实例的 p 读取时,将调用 Delegate 中的 getValue() 函数,所 以它第一个参数是读出 p 的对象、第二个参数保存了对 p 自身的描述(例如你可以取它的名字)。例如
val e = Example()
println(e.p)
输出结果:
Example@33a17727, thank you for delegating ‘p’ to me!
类似地,当我们给 p 赋值时,将调用 setValue() 函数。前两个参数相同,第三个参数保存将要被赋予的值
e.p = "NEW" 输出结果:
NEW has been assigned to ‘p’ in Example@33a17727.
自 Kotlin 1.1 起你可以在函数或代码块中声明一个委托属性,因此它不一定是类的成员
3.标准委托,Kotlin 标准库为几种有用的委托提供了工厂方法。
4.延迟属性 Lazy
lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性 的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果,后续调用get() 只是返回记录的结果。
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println (lazyValue)//只输出Hello
}
默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有 线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将
LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。而如果你确定初始化将 总是发生在与属性使用位于相同的线程,那么可以使用 LazyThreadSafetyMode.NONE 模式:它不 会有任何线程安全的保证以及相关的开销。
5.可观察属性 Observable
Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。每当我们给属性赋 值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
//如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代observable() 。在属性被赋新值生 效之前会调用传递给 vetoable 的处理程序
6.把属性储存在映射中
一个常⻅的用例是在一个映射(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
}
7.局部委托属性(自1.1起)
你可以将局部变量声明为委托属性。例如,你可以使一个局部变量惰性初始化
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
//memoizedFoo变量只会在第一次访问时计算。如果someCondition失败,那么该变量根本不会计算。
8.属性委托要求
这里我们总结了委托对象的要求。
8.1 对于一个只读属性(即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()
}
}
8.2 对于一个可变属性(即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
}
}
}
getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。当你 需要委托属性到原本未提供的这些函数的对象时后者会更便利。两函数都需要用 operator 关键字来进行标记
8.3 委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口 之一。这俩接口是在 Kotlin 标准库中声明的:
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)
}
9.翻译规则
在每个委托属性的实现的背后,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 自身。
请注意,直接在代码中引用绑定的可调用引用的语法 this::prop 自 Kotlin 1.1 起才可用。
10. 提供委托(自1.1 起)
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。如果 by 右侧所使 用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。
provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检测属 性一致性。
例如,如果要在绑定之前检测属性名称,可以这样写:
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)
// 创建委托
return 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<*> 或其超类型。
//在创建 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)
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
//请注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为getter或setter生成的代码。
}
kotlin类与对象——>委托、委托属性的更多相关文章
- C#用反射实现两个类的对象之间相同属性的值的复制
在进行实体转换操作的时候如果需要在对两个实体之间两个属性字段差不多相同的类要进行一个互相的转换,我们要把a对象的所有字段的值都复制给b对象,我们只能用b.属性=a.属性来写,如果属性字段太多的话,就要 ...
- Kotlin 类和对象
类定义 Kotlin 类可以包含:构造函数和初始化代码块.函数.属性.内部类.对象声明. Kotlin 中使用关键字 class 声明类,后面紧跟类名: class Runoob { // 类名为 R ...
- JAVA:类,对象,成员属性,成员方法,构造方法,类变量,类方法<2>
一.类的定义 一个全面的类定义是比较复杂的, 定义如下:
- Java学习第二篇:类,对象,成员属性,成员方法,构造方法,类变量,类方法
一.类的定义 一个全面的类定义是比较复杂的, 定义如下:
- [转]C#利用反射实现两个类的对象之间相同属性的值的复制
本文转自:https://blog.csdn.net/u013093547/article/details/53584591 在使用c#进行程序编写时,会遇到一个问题,两个属性字段差不多相同的类要进行 ...
- mysqli类的对象和其属性方法;mysqli类面向过程的属性和方法
mysqli 方法的概述 mysqli 类 面向对象接口 面向过程接口 描述 属性 $mysqli::affected_rows mysqli_affected_rows() 获取上次 Mysql ...
- C#利用反射实现两个类的对象之间相同属性的值的复制
http://blog.csdn.net/u013093547/article/details/53584591 今天在拷贝对象的时候,看着代码实在是有点烦,一堆一样的代码,还是找找有没有直接反射拷贝 ...
- swift基础:第六部分:类与对象
http://reactnative.cn/docs/0.24/getting-started.html#content(react Native 开发文档) 互联网这个时代,你松懈一天,就会有很多很 ...
- Python类和对象
目录 类与对象 其他方法 继承 经典类和新式类 派生 组合 接口.归一化设计与抽象类 继承实现的原理 子类中调用父类的方法 指名道姓 super()方法 多态与多态性 封装 单下划线开头 双下划线开头 ...
- python面向对象:类空间,对象空间, 组合
一. 类空间,对象空间 1. 类空间,对象空间 创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性 而类有两种属性:静态属性和动态属性 静态属性就是直接在类中定义的变 ...
随机推荐
- 【MUI】工作总结
1.快速创建页面结构: mDoctype HTML: <!doctype html> <html lang="en"> <head> <m ...
- 【Hibernate】Re01.5 API
1.Session单表的CRUD操作 1.增加或者修改,使用同一个方法,或者下面的两个也行: 感觉多此一举... 2.删除方法,硬删除: 3.获取方法提供了两种,Get & Load get方 ...
- 人形机器人(humanoid)(双足机器人、四足机器人)—— 操控员 —— 机器人数据收集操作员
参考: https://www.youtube.com/watch?v=jbQ4M4SNb2M 机器人数据收集操控员,就和大模型训练数据收集员.数据类型标识员(打标签人员)一样,都是为了人工生成AI训 ...
- [POI2015] MOD 题解
前言 题目链接:洛谷. 题意简述 给定一棵树,求断掉一条边再连上一条边所得的新树直径最小值和最大值,以及相应方案(你可以不进行任何操作,即断掉并连上同一条边). 题目分析 假设我们枚举断掉某一条边,得 ...
- 15-canvas渐变色
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...
- 别再被坑了! JavaScript类型检测的最佳实践
别再被坑了! JavaScript类型检测的最佳实践 在 JavaScript 中,我们经常需要判断一个变量的类型.这个需求在编程中非常常见,因为不同类型的数据会影响到我们的代码逻辑. JavaScr ...
- 自签名证书实现浏览器IP证书访问
创建文件夹 mkdir ssl cd ssl #IP.2 改成自己电脑的IP地址或服务器IP vi ext.ext keyUsage = nonRepudiation, digitalSignatur ...
- 获取微信小程序页面路径
2024/07/12 1.步骤 2.注意事项 3.参考 1.步骤 微信公众号关联小程序时需要用到小程序的页面路径,获取步骤如下:' 登录微信公众平台--工具--生成小程序码--获取更多页面路径--填写 ...
- Linux下编程常用基本命令
Linux常用命令 基本操作命令 ls命令 ls -l # 列出文件和目录的详细信息 ls -R # 列出所有文件 ls -a # 列出隐藏文件 l;s # 列出 mv命令 mv example.tx ...
- 关于vscode自动格式化的坑(Prettier - Code formatter)
在入坑vscode的时候在网上找了一些扩展包,其中有一款名为Prettier - Code formatter的代码格式化工具,其作用为当按下ctrl+s的时候自动进行格式化(当你进行格式化操作的时候 ...