【对比Java学Kotlin】代理
Java 代理
当我们需要对某个对外公开的 API 做一些拦截时,我们可以使用代理。常见的应用场景包括内部校验、将已废弃但是又无法删除的方法委托给新的代理类处理等(有点挂羊头卖狗肉的意思)。比如:
// 内部校验
public void invoke() {if (mDelegateObject.isValid()) {// omitted}
}// 将已废弃的方法委托给最新的代码
@Deprecated
public void deprecatedFunc() {mDelegateObject.recommendedFunc() // 将老方法委托给新方法完成相关功能
}
但是如果外部引用的不是公共方法,而是直接引用了某个成员属性,这时事情就麻烦了,因为我们在 Java 中无法对属性完成委托。
从上面的例子中我们可以看出,代理对 Java 语言来说是一种设计模式,Java 语言本身并未提供委托相关的支持,而且使用 Java 无法完成对属性的委托。而我们今天的主角 Kotlin 则对上述问题做了改进。
Kotlin 代理
在 Kotlin 中,我们使用关键字 by
使用委托功能。
代理方法
Kotlin 对方法的委托是借助接口来实现的。具体用法如下:
interface Base {fun print()
}class BaseImpl(val i : Int) : Base {override fun print() {println(i)}
}class Derived(b: Base) : Base by bfun main() {val b = BaseImpl(10)Derived(b).print() // 输出 10
}
我们来解读下上面的代码。首先,Base 是个接口,而 Derived 这个类继承了这个接口,看起来也没有实现 print()
方法,编译器之所以没报错,是因为我们使用了 by
关键字将其本应实现的 print()
方法委托给了 b,即最终执行的是 b.print()
。
注意,只支持接口,不支持抽象类。
代理属性
Kotlin 不仅支持方法代理,还支持属性代理。我们知道对类成员属性的操作无非就是读写两种。我们可以对类属性的读写过程进行拦截以实现委托功能。
其实说到对属性的拦截,我们回忆一下 Kotlin 属性是有默认的 getter&setter 的,我们重写这两个方法也可以实现对属性读写操作的拦截,也能实现委托功能。当我们只有一个属性时这种用法还好,但是当我们面对多个属性都要重写其 getter&setter 时难免会产生模板代码,而 Kotlin 提供的委托能力则可以避免这种问题的发生。
自定义委托
我们要对类属性的读写操作实现委托,只要重写两个接口即可:
public interface ReadOnlyProperty<in R, out T> {public operator fun getValue(thisRef: R, property: KProperty<*>): T
}public interface ReadWriteProperty<in R, T> {public operator fun getValue(thisRef: R, property: KProperty<*>): Tpublic operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
thisRef
是被代理属性所在类的类型,getValue()
的返回值类型要和被代理的属性类型一致(相同或子类型)。
从 ReadOnlyProperty
和 ReadWriteProperty
命名上也可以看出,分别对应只读类型(val)和可变类型(var)。我们定义一个实现上述接口的代理类,然后使用 by
关键字连接代理类实例即可:
class MyDelegate : ReadWriteProperty<Demo, String> {override operator fun getValue(thisRef: Demo, property: KProperty<*>): String {return "hi, $thisRef, thank you for delegating '${property.name}' to me!"}override operator fun setValue(thisRef: Demo, property: KProperty<*>, value: String) {println("hi, $value has been assigned to '${property.name}' in $thisRef.")}
}class Demo {var d: String by MyDelegate()
}fun main() {/* 输出结果:
** hi, xx has been assigned to 'd' in Demo@214c265e.
** hi, Demo@214c265e, thank you for delegating 'd' to me!
*/val d = Demo()d.d = "xx"print(d.d)
}
当然,我们也忽略这两个接口,然后自定义类来重写 getValue() 和 setValue() 方法,也是可行的:
class MyDelegate {operator fun getValue(thisRef: Any?, property: KProperty<*>): String {return "$thisRef, thankkkk 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 Demo {var d: String by MyDelegate()
}fun main() {val d = Demo()d.d = "xx"print(d.d)
}
究其原理,是编译器将属性的 getter&setter 方法委托给了操作符方法 getValue()&setValue()。
其实委托不仅限于类的成员属性,top-level 类型的变量和局部变量也是适用的,只不过此时的 thisRef
是 null:
import kotlin.reflect.KProperty
import kotlin.properties.ReadWritePropertyvar topLevelProp : String by Delegate()class Delegate {operator fun getValue(thisRef: Any?, property: KProperty<*>): String {return "$thisRef, thankkkk 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.")}
}fun main() {topLevelProp = "yyy" // 输出 yyy has been assigned to 'topLevelProp' in null.
}
内置委托
除了自定义的委托能力,Kotlin 还根据常见的场景给我们内置了一些针对属性的标准的委托功能:
- 懒加载,关键字
by lazy
,只在首次引用的时候初始化一次,后续引用均使用初始化后的结果; - 监听功能,当可变变量发生改变时通知监听者;
- 将属性存在map中,将变量值与map中的key-value值对应起来,方便读取;
【懒加载】
对于 by lazy
,lazy 是 Kotlin 标准库里面的函数,入参是一个线程安全模式和 lambda 表达式。线程安全模式有LazyThreadSafetyMode.SYNCHRONIZED
(默认值)、LazyThreadSafetyMode.PUBLICATION
和 LazyThreadSafetyMode.NONE
。
val lazyValue: String by lazy (LazyThreadSafetyMode.SYNCHRONIZED, {val l = System.currentTimeMillis()l.toString()
})fun main() {println(lazyValue) // 输出值是 1634459597319repeat(10000) {} // 经历一段耗时操作println(lazyValue) // 输出值仍然是首次初始化的结果 // 输出值是 1634459597319
}
【监听属性变动】
我们可以用 kotlin.properties.Delegates.observable() 方法来监听属性的变化,这样可以在值发生改变之后获得回调,属于 afterChange,observable() 方法接受的入参有两个:初始值和一段可执行代码;如果我们想在属性值发生之前就获取通知,可以用 kotlin.properties.Delegates.vetoable()。
import kotlin.properties.Delegatesclass Demo {var prop: String by Delegates.observable("no value", {_, old, new -> println("$old -> $new")})var veto: String by Delegates.vetoable("no value") { _, _, new ->new.isNotEmpty()}
}fun main() {val d = Demo()d.prop = "hello, Delegates.observable"d.veto = ""print(d.veto) // 输出 no value
}
kotlin.properties.Delegates.vetoable()
方法接受两个入参,首先是属性的初始值 “no value”,然后是一个 lambda 表达式,其返回值是一个布尔值,true 表示对属性值的修改成功,否则表示不允许修改,其值保持不变,我们示例代码中就是不让 veto 属性接受空值,如果是发现外部赋值为空则直接忽略。
查看 observable() 和 veto() 的源码我们可以发现其实二者都是包装 kotlin.properties.ObservableProperty
实现的,我们也可以直接使用这个抽象类来完成对属性变动的监听。
【属性存储在map中】
Kotlin 还提供了一种让类的成员属性正向map中的key-value的功能,我们来看下:
data class User(val map: Map<String, Any?>) { // Mapval name by map // valval age by map // val
}fun main() {val user = User(mapOf("name" to "linus", "age" to 99)) // mapOf()print("name: ${user.name}, age: ${user.age}")
}
上述代码的输出结果为 “name: linus, age: 99”。
注意,User.name 和 User.age 都是只读类型的属性,这个只读类型的 Map 是对应的。如果属性是可变类型的,则 map 类型需要是可变类型的 MutableMap:
data class User(val map: MutableMap<String, Any?>) { // MutableMapvar name by map // varvar age by map // var
}fun main() {val user = User(mutableMapOf("name" to "linus", "age" to 99)) // mutableMapOfprint("name: ${user.name}, age: ${user.age}")
}
属性之间委托
Kotlin 还支持直接将某个属性委托给另外一个属性,这里的属性类别包括top-level属性、类的成员属性、类的扩展属性,两个属性可以上述类别中的任何一个。具体的用法是 by
关键字和操作符::
,比如:
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
【对比Java学Kotlin】代理相关推荐
- 【对比Java学Kotlin】协程简史
文章目录 一.概念释义 1.1 协程定义 1.2 与线程的关系 1.3 协程简史 二.种类划分 2.1 按调用栈分类 2.2 按调度方式分类 三.异步编程 3.1 多线程 3.2 回调 3.3 Pro ...
- 【对比Java学Kotlin】object 关键字
两种用法 Kotlin 的 object 关键字有两种用法,一个是作为右值表达式的前缀,一个是作为类的前缀修饰符. object 表达式 object 表达式一般用于对现有类进行稍微修改.因为是临时使 ...
- 从Java到Kotlin(三)
本篇文章主要对比Java跟Kotlin中的类和接口的写法. 目录 一.类的声明 二.构造函数 三.函数的参数 四.创建类的实例 五.数据类 六.枚举类 七.属性 八.内部类 九.可见性修饰符 十.继承 ...
- 建造者模式(Java与Kotlin版)
前文推送 设计模式 简单工厂模式(Java与Kotlin版) 工厂方法模式(Java与Kotlin版) 抽象工厂模式(Java与Kotlin版) Kotlin基础知识 Kotlin入门第一课:从对比J ...
- Java和kotlin的对比
0.序言 在java的既有能力上学习kotlin,可快捷理解新语言特性.总体而言kotlin的语言设计思想是悲观谨慎,相对java的就比较乐观开放. 1.数据类型 Kotlin类型 位宽度 Java类 ...
- 手把手教你学Kotlin (2):task1-6 函数,Java to Kotlin Convert,(持续更新中)
文章目录 task1:函数 task2:Java to Kotlin Convert task3:Named arguments task1:函数 先看任务介绍: 这个任务的意思是修改代码,让函数返回 ...
- Java学到什么水平能够出去找工作!
Java学到什么水平能够出去找工作!搞定这些技术吧! 1.JavaSE内容 环境搭建,基础语法,面向对象,数组,集合,常用API,IO流,反射机制,多线程,网络编程 要求: 利用这些基础知识,写出一个 ...
- 一起学设计模式 - 代理模式
理模式(ProxyPattern)属于 结构型模式的一种,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 概述 身处华夏大地的 ...
- 从Java到Kotlin(五)
函数与Lambda表达式 目录 一.函数声明与调用 二.参数和返回值 三.单表达式函数 四.函数作用域 五.泛型函数 六.尾递归函数 七.中缀表示法 八.Lambda表达式的语法 九.高阶函数与Lam ...
- Java 调用 Kotlin
Kotlin 和 Java 的互操作性是 Kotlin 的一大优点,Kotlin 调用 Java 已经基本不需要特别去注意什么了,但是 Java 调用 Kotlin 代码就不那么好看了.项目切换到 K ...
最新文章
- 使用SAX解析XML文件
- C++基础--简单Socket通信实例
- ORA-12154:TNS:无法解析指定的连接标识符
- jq封装post请求数据_GitHub - xiaohange/JQHttpRequest: GET/POST / PUT / DELETE 网络请求的封装...
- 请求发送者与接收者解耦——命令模式
- 使用sqlserver搭建高可用双机热备的Quartz集群部署
- 【Oracle】Python 连接Oracle 数据库
- 天猫整站SSM-分页-总结(做个人学习笔记整理用)
- C/C++函数名修饰约定
- JVM 运行时数据区域总结
- 边框border(HTML、CSS)
- STL STL的不同实现版本
- 24. Django部署:项目部署
- 利用kd树实现最近邻搜索
- 机器学习实战+源代码
- JPA简介及其使用详解
- 【算法设计与分析】经典常考三十三道例题AC代码
- SQL Server的时态和历史表
- 智慧交通:地铁站 3D 可视化,车路协同赋能科学出行
- 解决复制大段英文文献到翻译软件出现的换行问题
热门文章
- 5款伊思儷超媒體繁体游戏 中文简体补丁
- 基于单片机的水壶自动加热系统_基于单片机电热水壶控制系统的设计
- Blender插件BoxCutter 7.1.7v15 硬表面建模2.91+教程Box Cutter
- html5允许属性值不使用引号,HTML5概述 - 阿振的个人空间 - OSCHINA - 中文开源技术交流社区...
- pycharm电脑上怎么下载-Pycharm下载和安装图文教程[超详细]
- java的多态是什么意思_【Java】基础18:什么叫多态?
- 将doc文件转为txt文件
- 如何对自己定义的目标进行分解
- LabVIEW编程LabVIEW开发 控制NI USB-6225例程与相关资料
- 广州药业vs加多宝 王老吉