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() 的返回值类型要和被代理的属性类型一致(相同或子类型)。

ReadOnlyPropertyReadWriteProperty 命名上也可以看出,分别对应只读类型(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.PUBLICATIONLazyThreadSafetyMode.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】代理相关推荐

  1. 【对比Java学Kotlin】协程简史

    文章目录 一.概念释义 1.1 协程定义 1.2 与线程的关系 1.3 协程简史 二.种类划分 2.1 按调用栈分类 2.2 按调度方式分类 三.异步编程 3.1 多线程 3.2 回调 3.3 Pro ...

  2. 【对比Java学Kotlin】object 关键字

    两种用法 Kotlin 的 object 关键字有两种用法,一个是作为右值表达式的前缀,一个是作为类的前缀修饰符. object 表达式 object 表达式一般用于对现有类进行稍微修改.因为是临时使 ...

  3. 从Java到Kotlin(三)

    本篇文章主要对比Java跟Kotlin中的类和接口的写法. 目录 一.类的声明 二.构造函数 三.函数的参数 四.创建类的实例 五.数据类 六.枚举类 七.属性 八.内部类 九.可见性修饰符 十.继承 ...

  4. 建造者模式(Java与Kotlin版)

    前文推送 设计模式 简单工厂模式(Java与Kotlin版) 工厂方法模式(Java与Kotlin版) 抽象工厂模式(Java与Kotlin版) Kotlin基础知识 Kotlin入门第一课:从对比J ...

  5. Java和kotlin的对比

    0.序言 在java的既有能力上学习kotlin,可快捷理解新语言特性.总体而言kotlin的语言设计思想是悲观谨慎,相对java的就比较乐观开放. 1.数据类型 Kotlin类型 位宽度 Java类 ...

  6. 手把手教你学Kotlin (2):task1-6 函数,Java to Kotlin Convert,(持续更新中)

    文章目录 task1:函数 task2:Java to Kotlin Convert task3:Named arguments task1:函数 先看任务介绍: 这个任务的意思是修改代码,让函数返回 ...

  7. Java学到什么水平能够出去找工作!

    Java学到什么水平能够出去找工作!搞定这些技术吧! 1.JavaSE内容 环境搭建,基础语法,面向对象,数组,集合,常用API,IO流,反射机制,多线程,网络编程 要求: 利用这些基础知识,写出一个 ...

  8. 一起学设计模式 - 代理模式

    理模式(ProxyPattern)属于 结构型模式的一种,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 概述 身处华夏大地的 ...

  9. 从Java到Kotlin(五)

    函数与Lambda表达式 目录 一.函数声明与调用 二.参数和返回值 三.单表达式函数 四.函数作用域 五.泛型函数 六.尾递归函数 七.中缀表示法 八.Lambda表达式的语法 九.高阶函数与Lam ...

  10. Java 调用 Kotlin

    Kotlin 和 Java 的互操作性是 Kotlin 的一大优点,Kotlin 调用 Java 已经基本不需要特别去注意什么了,但是 Java 调用 Kotlin 代码就不那么好看了.项目切换到 K ...

最新文章

  1. 使用SAX解析XML文件
  2. C++基础--简单Socket通信实例
  3. ORA-12154:TNS:无法解析指定的连接标识符
  4. jq封装post请求数据_GitHub - xiaohange/JQHttpRequest: GET/POST / PUT / DELETE 网络请求的封装...
  5. 请求发送者与接收者解耦——命令模式
  6. 使用sqlserver搭建高可用双机热备的Quartz集群部署
  7. 【Oracle】Python 连接Oracle 数据库
  8. 天猫整站SSM-分页-总结(做个人学习笔记整理用)
  9. C/C++函数名修饰约定
  10. JVM 运行时数据区域总结
  11. 边框border(HTML、CSS)
  12. STL STL的不同实现版本
  13. 24. Django部署:项目部署
  14. 利用kd树实现最近邻搜索
  15. 机器学习实战+源代码
  16. JPA简介及其使用详解
  17. 【算法设计与分析】经典常考三十三道例题AC代码
  18. SQL Server的时态和历史表
  19. 智慧交通:地铁站 3D 可视化,车路协同赋能科学出行
  20. 解决复制大段英文文献到翻译软件出现的换行问题

热门文章

  1. 5款伊思儷超媒體繁体游戏 中文简体补丁
  2. 基于单片机的水壶自动加热系统_基于单片机电热水壶控制系统的设计
  3. Blender插件BoxCutter 7.1.7v15 硬表面建模2.91+教程Box Cutter
  4. html5允许属性值不使用引号,HTML5概述 - 阿振的个人空间 - OSCHINA - 中文开源技术交流社区...
  5. pycharm电脑上怎么下载-Pycharm下载和安装图文教程[超详细]
  6. java的多态是什么意思_【Java】基础18:什么叫多态?
  7. 将doc文件转为txt文件
  8. 如何对自己定义的目标进行分解
  9. LabVIEW编程LabVIEW开发 控制NI USB-6225例程与相关资料
  10. 广州药业vs加多宝 王老吉