Kotlin代理属性--官方文档翻译
代理属性 Delegated Properties
本文为个人翻译的Kotlin官方文档, 原文连接: Delegated Properties
一些特定的常见类型的属性, 尽管我们可以在每次需要的时候实现他们, 但是如果我们一次把他们全部实现并放在一个库中, 这会非常方便, 包括:
- 延迟属性: 只在第一次访问的时候计算值
- 广播属性: 当属性的值改变时通知观察者
- 将数据存储在键值对中, 而不是独立的域中.
Kotlin提供的代理属性, 包含了这些(以及其他)例子:
class Example{
var p: String by Delegate()
}
语法是: val/var <property name>: <Type> by <expression>. 在by关键字后面的语句是delegate, 因为属性的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.'")
}
}
当我们从被代理给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.
关于代理对象的需求的说明可以在[这里]找到(delegated-properties.html#property-delegate-requirements).
需要注意的是从Kotlin 1.1之前你可以在方法或代码块中声明代理属性了, 代理属性不必声明为类的成员, 例子.
标准库中的代理 Standard Delegates
Kotlin标准库为一些常用的代理提供了工厂方法.
延迟属性 Lazy
lazy() 方法接收一个lamda作为参数并返回一个 Lazy<T>实例, 可以实现延迟加载:
第一次调用 get()时执行传入 lazy()的lambda表达式并保存结果, 后续对get()的调用只返回保存的结果.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
该例子输出:
computed!
Hello
Hello
默认情况下, 延迟属性的计算是同步的(synchronized): 只有一个线程计算该值, 其他的线程都会看见相同的值. 如果此初始化的步骤不需要同步, 多个线程可以同事执行初始化, 在lazy() 方法中传入LazyThreadSafetyMode.PUBLICATION作为参数.
如果可以确保初始化过程只会在单个线程中执行, 可以用LazyThreadSafetyMode.NONE模式, 该模式不保证线程安全, 避免相关的开销.
监控属性 Observable
Delegates.observable()有两个参数: 初始值和变化观察器.
每次代理属性被赋予值的时候都会调用观察器(在赋值操作之后).观察器有三个参数:属性类型, 旧值和新值.
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
该例子输出
<no name> -> first
first -> second
如果你想终端赋值的过程并拒绝赋值, 用vetoable()替代observable().
observable()的观察器参数是在赋值之前被调用的.
使用Mapc存储属性 Storing Properties in a Map
在map中存储属性是一种常见使用方式.
这种情形在解析JSON或者其他"动态的"事情时经常出现.
在这种情况下, 你可以使用map的实例来代理一个代理属性.
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
在这个例子中, 构造器接收一个map:
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
代理属性从这个map接收值(通过String类型的key --- 作为属性的名字)
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
当使用MutableMap而不是只读的Map时, 对var也可以使用.
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
本地代理属性 Local Delegated Properties (since 1.1)
你可以声明局部变量作为代理属性.
例如, 可以让局部变量成为lazy属性.
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo变量只会在第一次访问时计算.
如果someCondition失败了, 变量的值则完全不会进行计算.
属性代理的需求 Property Delegate Requirements
在此我们整理一下代理对象的需求.
对于一个只读属性(例如: val), 代理必须提供一个接收下列参数的getValue函数:
thisRef--- 必须是_属性拥有者_相同类型或者是其超类(对于扩展属性 --- 则是其所扩展的属性)property--- 必须是KProperty<*>类型或其超类,
这个函数必须返回和属性相同的类型, 或者其子类.
对于可变的属性(比如var), 代理必须额外提供具备下列参数的setValue函数:
thisRef--- 与getValue()相同,property--- 与getValue()相同,- new value --- 必须是与属性或其超类相同的类型
getValue()和/或setValue()函数可以用两种方式提供: 代理类的成员函数或者扩展函数.
后者在原有对象没有提供这些函数时非常方便.两种方式的函数都要使用operator关键字修饰.
代理类可以实现下面的接口之一, 包含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)
}
翻译规则 Translation Rules
在代理属性的背后, Kotlin编译器生成一个辅助属性并代理给它. 比如, 对于属性prop, 会生成一个prop$delegate辅助属性, 访问器的代码就是简单的代理给这个附加的属性:
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
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是prop自身的反射类型信息, 是KProperty类型.
提供代理 Providing a delegate (since 1.1)
通过定义provideDelegate操作符可以扩展创建属性实现所代理对象的逻辑.如果by右侧使用的对象定义了provideDelegate作为成员函数或者扩展函数, 这个函数会在创建属性代理时被调用.
One of the possible use cases of provideDelegate is to check property consistency when the property is created, not only in its getter or setter.
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<*>类型或其超类.
The provideDelegate method is called for each property during the creation of the MyUI instance, and it performs the necessary validation right away.
provideDelegate方法在每个MyUI实例创建期间都被调用, 并立即进行必要的检验.
如果没有这种在属性和其代理之间拦截的手段, 要明确的传入属性命, 这很不方便.
// 没有"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()的属性生成的代码和上面不提供provide Delegate 函数的代码.
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代理属性--官方文档翻译的更多相关文章
- 基本控件文档-UITextField属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...
- UIPikerView的属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/C ...
- 基本控件文档-UIView属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...
- 基本控件文档-UISwitch属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...
- 基本控件文档-UISlider属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/C ...
- 基本控件文档-UISegment属性----iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...
- 基本控件文档-UILabel属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/ ...
- 基本控件文档-UIButton属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/Ch ...
- UIImageView属性---iOS-Apple苹果官方文档翻译
本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址 //转载请注明出处--本文永久链接:http://www.cnblogs.com/C ...
随机推荐
- 某天U盘插在笔记本打不开了,是U盘坏了还是电脑的问题?
五六月份忙着毕业设计与毕业论文,U盘在这个时候就是大功臣啦! 然而打印完最终版本论文上交后,再次把U盘插在自己的笔记本上读取失败了... 只有一个空白的图标,打不开,也无法格式化. 试着删除,拔了又插 ...
- UglifyJS-- 对你的js做了什么
也不是闲着没事去看压缩代码,但今天调试自己代码的时候发现有点意思.因为是自己写的,虽然压缩了,格式化之后还是很好辨认.当然作为min的首要准则不是可读性,而是精简.那么它会尽量的缩短代码,尽量的保持一 ...
- VerilogHDL可综合设计的注意事项
可综合的语法已经记录得差不多了,剩下一些遗留的问题,在这里记录一下吧. 一.逻辑设计 (1)组合逻辑设计 下面是一些用Verilog进行组合逻辑设计时的一些注意事项: ①组合逻辑可以得到两种常用的RT ...
- CentOS安装配置MySql数据库
CentOS版本7.2,MySql版本5.7 1.下载MySql安装源 wget https://dev.mysql.com/get/mysql57-community-release-el7 ...
- 那些年,用C#调用过的外部Dll
经常有人找到我咨询以前在csdn资源里分享的dll调用.算算也写过N多接口程序.翻一翻试试写篇随笔. 明华IC读写器DLL 爱迪尔门锁接口DLL 通用OPOS指令打印之北洋pos打印机dll 明泰非接 ...
- 6-最基础的服务-es6写法
创建server.js 'use strict'; //http模块 var http = require('http'); //封装的方法 var handlers = require('./han ...
- 解决lxml不含etree模块导致scrapy startproject ***出错
本文环境:win10(64) python3.6(64) 背景:之前已成功安装scrapy(1.4.0),但在命令行敲 scrapy startproject ***出错,错误提示:from ... ...
- vue+mockjs 模拟数据,实现前后端分离开发
在项目中尝试了mockjs,mock数据,实现前后端分离开发. 关于mockjs,官网描述的是 1.前后端分离 2.不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据. 3.数据类型丰 ...
- Web如何应对流量劫持?
虽然互联网经过多年的发展,可是网站使用的底层协议仍是 HTTP,HTTP 作为一种明文传播协议,所有的传输数据都是明文,我们都知道在通信中使用明文(不加密) 内容可能会被窃听,同时网站存在被劫持的风险 ...
- linux文件系统目录解析
Linux下的文件系统为树形结构,入口为/ 树形结构下的文件目录: 无论哪个版本的Linux系统,都有这些目录,这些目录应该是标准的.各个Linux发行版本会存在一些小小的差异,但总体来说,还是大体差 ...