【Kotlin学习】运算符重载及其他约定——解构声明和组件函数、委托属性

解构声明和组件函数

解构声明

这个功能允许你展开单个复合值,并使用它来初始化多个单独的变量,并使用它来初始化多个单独的变量

对于数据类Point来说

在这里插入图片描述

此处声明的变量x,y可以用p的组件来初始化

一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。事实上,解构声明再次运用到了约定的原理。要在解构声明中初始化每个变量,将调用名为componentN的函数,其中N是声明中变量的位置。换句话说,前面的例子可以被转换成
val (a,b)=p -> val a = p.component1() val b = p.component2()
对于数据类,编译器为每个在主构造方法中声明的属性生成一个componentN函数

手动为非数据类声明这些功能

在这里插入图片描述

解构声明主要使用场景之一是从一个函数返回多个值。若要这么做,可以定义一个数据类来保存返回所需的值,并将它作为函数的返回类型

在这里插入图片描述

此时在外部就可以通过解构声明:val (name,extension)=splitName("111.xxx")

如果你注意到componentN函数在数组和集合上也有定义,可以进一步改进代码,当你在处理大小已知的集合时非常有用

使用解构声明来处理集合

在这里插入图片描述

componentN函数是无法无限定义的,标准库只允许使用此语法来访问一个对象的前五个元素

让一个函数能返回多个值有更简单的方法,使用标准库中的Pair和Triple类。在语义表达上这种方式会差一点,因为这些类也不知道它返回的对象中包含什么,但因为不需要定义自己的类所以可以少写代码

解构声明和循环

解构声明不仅能用作函数中的顶层语句,还可用在其他可以声明变量的地方,例如in循环

在这里插入图片描述

此处用到了两个约定:一个是迭代一个对象,一个是用于解构声明。kotlin标准库给map增加了一个扩展的iterator函数,用于返回map条目的迭代器。他还包含Map.Entry上的扩展函数component1和component2,分别返回它的键和值

重用属性访问的逻辑:委托属性

这个功能可以让你轻松实现这样的属性,它们处理起来比把值存储在支持字段中更复杂,却不用在每个访问其中都重复这样的逻辑。例如:这些属性可以把它们的值存储在数据库表中,在浏览器会话中,在一个map中等。

这个功能的基础是委托,这是一种设计模式,操作的对象不用自己执行,而是把工作委托给另一个辅助的对象。我们把辅助对象称为委托。当这个模式应用于一个属性时,它也可以将访问器的逻辑委托给一个辅助对象。你可以手动实现或利用kotlin的语言支持

委托属性的基本操作

基本语法

在这里插入图片描述

属性p将它的访问器逻辑委托给了另一个对象:这里是Delegate类的一个新的实例。通过关键字by对其后的表达式求值来获取这个对象,关键字by可以用于任何符合属性委托约定规则的对象。编译器创建一个隐藏的辅助属性,并使用委托对象的实例进行初始化,初始属性p会委托给该实例,为了简单起见称它为Delegate

编译器会进行如下操作

在这里插入图片描述

Delegate类必须具有getValuesetValue方法(后者仅适用于可变属性)。像往常一样,它们可以是成员函数也可以是扩展函数。下图是Delegate类的简单实现,getValue包含了实现getter的逻辑,setValue同理

在这里插入图片描述

此时创建一个Foo类对象foo,foo.p可作为普通的属性使用,而事实上它将调用Delegate类型的辅助属性的方法

使用委托属性:惰性初始化和"by lazy()"

惰性初始化是一种常见的模式,直到第一次访问该属性的时候,才根据需要创建对象的一部分。当初始化过程消耗大量资源并且在使用对象时并不总是需要数据时,这个很有用。

一个Person类可以用来访问一个人写的邮件列表。邮件存储在数据库中,访问比较耗时。你希望只有在首次访问时才加载邮件并只执行一次。假设你已经有函数loadEmails,用来从数据库中检索电子邮件

在这里插入图片描述

在这里插入图片描述

这里使用了支持属性技术,你有一个属性——_emails用来存储这个值和关联委托,而另一个emails用来提供对属性的读取访问。你需要使用两个属性,因为属性具有不同的类型。_emails可以为,而emails为非空。但上图的实现不是线程安全的

使用委托属性实现惰性初始化
在这里插入图片描述
lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。lazy的参数是一个lambda,可以调用它来初始化这个值。默认情况下lazy函数是线程安全的,如果需要可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步

实现委托属性

当一个对象的属性更改时通知监听器

在这里插入图片描述

PropertyChangeSupport类维护了一个监听器列表,并向它们发送PropertyChangeEvent事件。要使用它通常需要把这个类的一个实例存储为bean类的一个字段,并将属性更改的处理委托给它。为了避免在每个类中添加这个字段,你要创建一个小的工具类用来存储PropertyChangeSupport的实例并监听属性更改。之后你的类会继承这个工具类来访问changeSupport

在这里插入图片描述

使用

在这里插入图片描述

上方的写法其实有很多重复代码,我们可以把他们提取到辅助类

在这里插入图片描述

在这里插入图片描述

结合辅助类数属性委托

在这里插入图片描述

get/setValue函数被标记了operator
这些函数加了两个参数,一个用于接收属性的实例,用来设置或读取属性,另一个用于表示属性本身,这个属性类型为KProperty,可以使用KProperty.name访问该属性的名称

在这里插入图片描述

kotlin会自动将委托存储在隐藏的属性中,并在访问或修改属性时调用委托的getValue和setValue。我们不用手动实现可观察的属性逻辑,可以使用kotlin标准库,它已经包含了类似于ObservableProperty的类。标准库类和这里使用的PropertyChangeSupport类没有耦合,因此要传递一个lambda来告诉它如何通知属性值的改变

在这里插入图片描述

by右边的表达式不一定是新创建的实例,也可以是函数调用、另一个属性或任何其他表达式,只要这个表达式的值是能够被编译器用正确的参数类型来调用getValue和setValue的对象。get/setValue可以是对象自己声明的方法或扩展函数

委托属性的变换规则

假设你已经有了一个具有委托属性的类

在这里插入图片描述

MyDelegate实例会被保存到一个隐藏的属性中,它被称为<delegate>。编译器也将用一个KProperty类型的对象来代表这个属性,它被称为<property>

编译器生成的代码如下

在这里插入图片描述

因此,在每个属性访问器中,编译器都会生成对应的get/setValue方法
val x=c.prop -> val x = <delegate>.getValue(this,<property>)
c.prop=x -> <delegate>.setValue(this,<property>,x)

你可以自定义存储该属性值的位置(map/database/cookie等),以及在访问该属性时做点什么(添加验证,更改通知)。所有这一切都可以用紧凑的代码完成

在map中保存属性值

委托属性发挥作用的另一种常见用法,使用在有动态定义的属性集的对象中。这样的对象又是被称为自订对象。

例如考虑一个联系人管理系统,可以用来存储有关联系人的任意信息。系统中的每个人都有一些属性需要特殊处理,以及每个人特有的数量任意的额外属性。实现这种系统的一种方法是将人的所有属性存储在map中,不确定提供属性,来访问需要特殊处理的信息

定义一个属性,把值传到map

在这里插入图片描述

这里使用了一个通用API来把数据加载到对象中,然后使用特定API访问一个属性值

使用委托属性把值存到map中

在这里插入图片描述

因为标准库已经在标准Map和MutableMap接口上定义了getValue和setValue扩展函数,所以这里可以直接这样用。属性的名称将自动用作在map中的键,属性值作为map中的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值