# 委托属性
[TOC]
有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是如果能够为大家把他们只实现一次并放入一个库会更好。例如包括:
* 延迟属性(lazy properties): 其值只在首次访问时计算;
* 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
* 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
为了涵盖这些(以及其他)情况,Kotlin 支持 _委托属性_:
```kotlin
class Example {
var p: String by Delegate()
}
```
语法是: `val/var <属性名>: <类型> by <表达式>`。在 *by*{:.keyword} 后面的表达式是该 _委托_,因为属性对应的 `get()`(与 `set()`)会被委托给它的 `getValue()` 与 `setValue()` 方法。属性的委托不必实现任何的接口,但是需要提供一个 `getValue()` 函数(与 `setValue()`——对于 *var*属性)。
例如:
```kotlin
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` 自身的描述(例如你可以取它的名字)。 例如:
```kotlin
val e = Example()
println(e.p)
```
输出结果:
```
Example@33a17727, thank you for delegating ‘p’ to me!
```
类似地,当我们给 `p` 赋值时,将调用 `setValue()` 函数。前两个参数相同,第三个参数保存将要被赋予的值:
```kotlin
e.p = "NEW"
```
输出结果:
```
NEW has been assigned to ‘p’ in Example@33a17727.
```
委托对象的要求规范可以在[下文](http://www.kotlincn.net/docs/reference/delegated-properties.html#%E5%B1%9E%E6%80%A7%E5%A7%94%E6%89%98%E8%A6%81%E6%B1%82)找到。
请注意,自 Kotlin 1.1 起你可以在函数或代码块中声明一个委托属性,因此它不一定是类的成员。你可以在下文找到[其示例](http://www.kotlincn.net/docs/reference/delegated-properties.html#%E5%B1%80%E9%83%A8%E5%A7%94%E6%89%98%E5%B1%9E%E6%80%A7%E8%87%AA-11-%E8%B5%B7)。
## 标准委托
Kotlin 标准库为几种有用的委托提供了工厂方法。
### 延迟属性 Lazy
[`lazy()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/lazy.html) 是接受一个 lambda 并返回一个 `Lazy <T>` 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 `get()` 会执行已传递给 `lazy()` 的 lambda 表达式并记录结果,后续调用 `get()` 只是返回记录的结果。
```kotlin
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
```
默认情况下,对于 lazy 属性的求值是**同步锁的(synchronized)**:该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将`LazyThreadSafetyMode.PUBLICATION` 作为参数传递给 `lazy()` 函数。而如果你确定初始化将总是发生在与属性使用位于相同的线程,那么可以使用 `LazyThreadSafetyMode.NONE` 模式:它不会有任何线程安全的保证以及相关的开销。
### 可观察属性 Observable
[`Delegates.observable()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/observable.html) 接受两个参数:初始值与修改时处理程序(handler)。
每当我们给属性赋值时会调用该处理程序(在赋值*后*执行)。它有三个参数:被赋值的属性、旧值与新值:
```kotlin
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()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.properties/-delegates/vetoable.html) 取代 `observable()`。
在属性被赋新值生效*之前*会调用传递给 `vetoable` 的处理程序。
## 把属性储存在映射中
一个常见的用例是在一个映射(map)里存储属性的值。这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
```kotlin
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
```
在这个例子中,构造函数接受一个映射参数:
```kotlin
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
```
委托属性会从这个映射中取值(通过字符串键——属性的名称):
```kotlin
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
//sampleStart
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
//sampleEnd
}
```
这也适用于 *var*{:.keyword} 属性,如果把只读的 `Map` 换成 `MutableMap` 的话:
```kotlin
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
```
## 局部委托属性(自 1.1 起)
你可以将局部变量声明为委托属性。例如,你可以使一个局部变量惰性初始化:
```kotlin
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
```
`memoizedFoo` 变量只会在第一次访问时计算。如果 `someCondition` 失败,那么该变量根本不会计算。
## 属性委托要求
这里我们总结了委托对象的要求。
对于一个**只读**属性(即 *val* 声明的),委托必须提供一个名为 `getValue` 的函数,该函数接受以下参数:
* `thisRef` —— 必须与 _属性所有者_ 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
* `property` —— 必须是类型 `KProperty<*>` 或其超类型。
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个**可变**属性(即 *var*{:.keyword} 声明的),委托必须*额外*提供一个名为 `setValue` 的函数,该函数接受以下参数:
* `thisRef` —— 同 `getValue()`;
* `property` —— 同 `getValue()`;
* new value —— 必须与属性同类型或者是它的子类型。
`getValue()` 或/与 `setValue()` 函数可以通过委托类的成员函数提供或者由扩展函数提供。当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。两函数都需要用 `operator` 关键字来进行标记。
委托类可以实现包含所需 `operator` 方法的 `ReadOnlyProperty` 或 `ReadWriteProperty` 接口之一。这俩接口是在 Kotlin 标准库中声明的:
```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)
}
```
### 翻译规则
在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。
例如,对于属性 `prop`,生成隐藏属性 `prop$delegate`,而访问器的代码只是简单地委托给这个附加属性:
```kotlin
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` 自身。
请注意,直接在代码中引用[绑定的可调用引用](http://www.kotlincn.net/docs/reference/reflection.html#%E7%BB%91%E5%AE%9A%E7%9A%84%E5%87%BD%E6%95%B0%E4%B8%8E%E5%B1%9E%E6%80%A7%E5%BC%95%E7%94%A8%E8%87%AA-11-%E8%B5%B7)的语法 `this::prop` 自 Kotlin 1.1 起才可用。
### 提供委托(自 1.1 起)
通过定义 `provideDelegate` 操作符,可以扩展创建属性实现所委托对象的逻辑。
如果 `by` 右侧所使用的对象将 `provideDelegate` 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。
`provideDelegate` 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检测属性一致性。
例如,如果要在绑定之前检测属性名称,可以这样写:
```kotlin
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` 方法,并立即执行必要的验证。
如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能,你必须显式传递属性名,这不是很方便:
```kotlin
// 检测属性名称而不使用“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()` 生成的代码与[上面](http://www.kotlincn.net/docs/reference/delegated-properties.html#%E7%BF%BB%E8%AF%91%E8%A7%84%E5%88%99)(当 `provideDelegate` 方法不存在时)生成的代码:
```kotlin
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简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android