Kotlin基础知识_10-泛型和委托
Kotlin基础知识_10-泛型和委托
1. 泛型
1.1 泛型类与泛型方法
泛型类
class HttpResponse<T> {
fun parseResponse(inputValue: T): T {
return inputValue
}
}
fun main() {
val httpResponse = HttpResponse<String>()
val response = httpResponse.parseResponse("success")
println("response value: $response")
}
使用时,只需要在类名后加上<T>
即可。
泛型方法
fun <T> buildHttpRequest(seq: T): String {
val ret = StringBuilder().run {
append("seq: $seq\n")
append("msgLength: 100\n")
append("msgContent: 123ABC\n")
toString()
}
return ret
}
fun main() {
val reqMsg = buildHttpRequest(20)
println("reqMsg: $reqMsg")
}
1.2 对泛型进行限定
ex: 限定只能输入数字
2. 委托
委托机制是一种非常灵活的语言特性,它可以让我们将对象的某些属性或方法委托给其他对象来处理。Kotlin 也支持委托功能,分为类委托和属性委托。
2.1 类委托
类委托就是把类中的某些方法给委托出去,由委托类去完成。
比如,身为学生就需要写家庭作业:
interface Student {
fun doHomeWork()
}
雷军同学是个好学生,他马上就把作业完成了:
class LeiJun : Student {
override fun doHomeWork() {
println("My name is LeiJun, I have completed the homework")
}
}
但马云同学是个坏学生,他只想着玩游戏,没办法完成家庭作业,所以他只好委托雷军同学帮他完成家庭作业:
class MaYun(val leiJun: LeiJun) : Student {
fun playGame() {
println("My name is MaYun, I like play Games")
}
override fun doHomeWork() {
leiJun.doHomeWork()
}
}
那这样马云同学就High了, 他既可以玩游戏,也可以完成家庭作业:
interface Student {
fun doHomeWork()
}
class LeiJun : Student {
override fun doHomeWork() {
println("My name is LeiJun, I have completed the homework")
}
}
class MaYun(val leiJun: LeiJun) : Student {
fun playGame() {
println("My name is MaYun, I like play Games")
}
override fun doHomeWork() {
leiJun.doHomeWork()
}
}
fun main() {
val leiJun = LeiJun()
val maYun = MaYun(leiJun)
leiJun.doHomeWork()
maYun.playGame()
maYun.doHomeWork()
}
输出:
My name is LeiJun, I have completed the homework
My name is MaYun, I like play Games
My name is LeiJun, I have completed the homework
可以看到,马云同学在雷军的协助下,也能完成家庭作业(尽管作业实际上不是由他完成的...不过无所谓, 反正也能做家庭作业了)。
这里:
MaYun
类将doHomeWork()
的方法实现委托给了LeiJun
去做,从而也能顺利调用doHomeWork()
的实现逻辑;- 当调用
MaYun
类的doHomeWork()
方法时,实际上是调用了LeiJun
类的doHomeWork()
方法;
上面就是一个简单的委托实现, 但是写起来还是比较麻烦(可能你现在还没有感觉到,但是如果 Student
接口中定义了很多接口方法时,若是所有接口方法都要由委托类去实现,那就麻烦了),为此,kotlin中引入了一个 by关键字,借助该关键字可以快速实现上述委托模型,上述code中的MaYun
类使用kotlin中的by
关键字可以改写成:
class MaYun1 : Student by LeiJun() // 方式二
class MaYun2(delegate: Student) : Student by delegate // 方式三
测试:
interface Student {
fun doHomeWork()
}
class LeiJun : Student {
override fun doHomeWork() {
println("My name is LeiJun, I have completed the homework")
}
}
// 方式一
class MaYun(val leiJun: LeiJun) : Student {
fun playGame() {
println("My name is MaYun, I like play Games")
}
override fun doHomeWork() {
leiJun.doHomeWork()
}
}
class MaYun1 : Student by LeiJun() // 方式二
class MaYun2(delegate: Student) : Student by delegate // 方式三
fun main() {
MaYun(LeiJun()).doHomeWork() // 方式一
MaYun1().doHomeWork() // 方式二
MaYun2(LeiJun()).doHomeWork() // 方式三
}
输出:
My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework
以上三种方式均可以正常输出 doHomeWork()
的内容。
当然,如果仍然要保留 playGame()
,也可以在类体里面添加:
MaYun1.kt
// 方式二
class MaYun1 : Student by LeiJun() {
fun playGame() {
println("My name is MaYun, I like play Games")
}
}
MaYun2.kt
// 方式三
class MaYun2(val delegate: Student) : Student by delegate {
fun playGame() {
println("My name is MaYun, I like play Games")
}
fun whoHelpDoHomework() {
println("The ${delegate.javaClass} help me do homework")
}
}
可以看到方式三比方式二有一个优势,就是能够获取委托对象的实例,比如我以方式三的形式调用 whoHelpDoHomework()
方法,打印出委托对象的实例:
Test.kt
interface Student {
fun doHomeWork()
}
class LeiJun : Student {
override fun doHomeWork() {
println("My name is LeiJun, I have completed the homework")
}
}
// 方式一
class MaYun(val leiJun: LeiJun) : Student {
fun playGame() {
println("My name is MaYun, I like play Games")
}
override fun doHomeWork() {
leiJun.doHomeWork()
}
}
// 方式二
//class MaYun1 : Student by LeiJun() {
//}
// 方式三
//class MaYun2(delegate: Student) : Student by delegate {
//
//}
fun main() {
MaYun1().playGame()
MaYun1().doHomeWork() // 方式二
MaYun2(LeiJun()).doHomeWork()
MaYun2(LeiJun()).whoHelpDoHomework()
}
运行:
My name is MaYun, I like play Games
My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework
The class com.yongdaimi.kotlin.LeiJun help me do homework
可以看到,能够顺利打印出委托对象的实例:com.yongdaimi.kotlin.LeiJun
这里还有一点需要注意,就是 MaYun1
和 MaYun2
不能定义在顶层文件里,否则编译器会报错:
如果想继续在 MaYun1
和 MaYun2
中编写方法体,则需要另外新建两个kotlin类:MaYun1
和 MaYun2
, 我上面就是新建了两个kotlin类:MaYun1.kt
和 MaYun2.kt
。
2.2 属性委托
2.2.1 属性委托的含义
类委托可以完成接口方法的委托,除此之外,kotlin还支持属性的委托,语法模板如下:
val/var <属性名>: <类型> by <表达式>
类委托是把接口方法委托出去,那属性委托是委托什么呢?很简单,就是把属性委托出去,对于一个属性,无非有两个操作:获取属性以及修改属性,也就是对应的 get()方法和set()方法 。
属性委托即是将对应的 get()/set() 操作分发给指定委托对象的 getValue()/setValue()
方法执行;当然,如果是 val
修饰的属性,只需要提供 getValue()
即可,因为这个值不可变,也就不需要setValue()
了,
比如下面的例子,我们声明了一个Dog
类 和一个Person
类,人需要去看狗是否还有吃的,如果没有的话,可以给狗再喂一些吃的:
class Person {
// 对应属性中的get(), 表示获取数据
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, ${property.name}"
}
// 对应属性中的set(), 表示设置数据,只有var修饰的属性会有
operator fun setValue(thisRef: Any?, property: KProperty<*>, food: String) {
println("$thisRef, ${property.name}, $food")
}
}
class Dog {
var food: String by Person()
}
fun main() {
val dog = Dog()
println(dog.food) // 相当于调用 getValue()
dog.food = "肉" // 相当于调用 setValue()
}
运行:
com.yongdaimi.kotlin.Dog@13221655, food
com.yongdaimi.kotlin.Dog@13221655, food, 肉
上面例子中的 var food: String by Person()
就是将狗的food
属性委托给了Person
类,当调用Dog
的 getter/setter方法时,会自动调用Person
类的 getValue()
和 setValue()
方法。
在上面getValue()
和setValue()
的方法中:
- thisRef: 代表被委托对象:
Dog
; - property: 代表被委托对象的属性的元数据,即
food
属性的元数据,这些元数据被封装到了KProperty<*>
类中(这里的*号可以看做是java泛型里面的?号),这些元数据包括被委托对象的属性的名称, 类型,可见性等信息。 - value: 代表
setValue()
中要设置的值。
上述委托类Person
中的 getValue()
和 setValue()
方法需要我们手动编写,有点麻烦,kotlin为我们提供了 ReadOnlyProperty
, ReadWriteProperty
接口来简化这一操作:
2.2.2 ReadOnlyProperty 和 ReadWriteProperty
这两个接口位于 package kotlin.properties, Interfaces.kt 下:
ReadOnlyProperty
/**
* Base interface that can be used for implementing property delegates of read-only properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param T the type of object which owns the delegated property.
* @param V the type of the property value.
*/
public fun interface ReadOnlyProperty<in T, out V> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public operator fun getValue(thisRef: T, property: KProperty<*>): V
}
ReadWriteProperty
/**
* Base interface that can be used for implementing property delegates of read-write properties.
*
* This is provided only for convenience; you don't have to extend this interface
* as long as your property delegate has methods with the same signatures.
*
* @param T the type of object which owns the delegated property.
* @param V the type of the property value.
*/
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
/**
* Returns the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @return the property value.
*/
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
/**
* Sets the value of the property for the given object.
* @param thisRef the object for which the value is requested.
* @param property the metadata for the property.
* @param value the value to set.
*/
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
使用示例:
class PersonR : ReadOnlyProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "$thisRef, ${property.name}"
}
}
class PersonRW : ReadWriteProperty<Any, String> {
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "$thisRef, ${property.name}"
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
println("$thisRef, ${property.name}, $value")
}
}
class Dog {
val food: String by PersonR()
var bread: String by PersonRW()
}
fun main() {
val dog = Dog()
println(dog.food) // 相当于调用 PersonR #getValue()
println(dog.bread) // 相当于调用 PersonRW #getValue()
dog.bread = "黄油面包" // 相当于调用 PersonRW #setValue()
}
运行:
com.yongdaimi.kotlin.Dog@87aac27, food
com.yongdaimi.kotlin.Dog@87aac27, bread
com.yongdaimi.kotlin.Dog@87aac27, bread, 黄油面包
上面我们Dog
类新加一个面包属性 break
, 可以看到,可以通过实现 ReadOnlyProperty
与 ReadWriteProperty
接口来简化委托属性的实现,通过这种方式与手动实现的方式是一致的,但是比手动实现更快捷,也不易出错。
Note:有一点需要注意,在导入
ReadOnlyProperty
和ReadWriteProperty
接口时,注意使用的是package kotlin.properties
包下的接口,而不是javafx.beans.property
包下的,接口名虽然一样,但是效果是不一样的。
3. Kotlin 标准库提供的几种委托
kotlin 标准库中也提供了几个委托:
- 延迟委托:其值只有首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性存储到一个映射(map)中,而不是每个存在单独的字段中。
3.1 延迟委托 (by lazy)
延迟委托会用到kotlin内置的lazy()
函数, 这是一个高阶函数,它接收一个Lambda表达式,并返回一个Lazy<T>
实例,返回的实例可以作为实现延迟属性的委托对象。
该函数原型如下:
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and thread-safety [mode].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned instance uses itself
* to synchronize on. Do not synchronize from external code on the returned instance as it may cause accidental deadlock.
* Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
使用示例:
class Father {
val age by lazy {
println("Get father's age...")
40
}
}
fun main() {
val father = Father()
println("Father's age: ${father.age}")
println("Father's age: ${father.age}")
println("Father's age: ${father.age}")
}
运行:
Get father's age...
Father's age: 40
Father's age: 40
Father's age: 40
可以看到,第一次调用时,会执行Lambda表达式,并记录Lambda表达式返回的结果,后面再读取 age
属性时就直接返回之前记录的结果。
另外在调用 lazy()
函数时还可以传入一个 LazyThreadSafetyMode
参数,有三个取值,分别代表不同的意思:
- LazyThreadSafetyMode.SYNCHRONIZED:该值只在一个线程中计算,并且所有线程会看到相同的值。
- LazyThreadSafetyMode.PUBLICATION:可以在并发访问未初始化的
Lazy
实例值时调用多次,但只有第一次返回的值将被用作Lazy
实例的值。 - LazyThreadSafetyMode.NONE:不会有任何线程安全的保证以及相关的开销,当初始化与使用总是位于相同线程中时使用。
如果lazy
属性的求值时不传 LazyThreadSafetyMode
参数,那么默认情况下走的是是LazyThreadSafetyMode.SYNCHRONIZED
模式。
ex:
val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("Hello,第一次调用才会执行我!")
"10086"
}
3.2 可观察属性 - observable
如果你要监听属性值的变化,可以将该属性委托给 Delegates.observable
, Delegates.observable()
可以认为是一个属性监听器,当监听的属性值发生变更时会收到通知。其接收两个参数:初始值initialValue
与onChange()函数
,当属性被赋值后就会触发onChange()
,内部有三个参数:被赋值的属性、旧值与新值。
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
使用示例:
class Father {
var nickName: String by Delegates.observable("unknown name") { proerty, oldName, newName ->
println("${proerty.name}, changed from $oldName to $newName")
}
}
fun main() {
val father = Father()
father.nickName = "老猫"
father.nickName = "丁棍"
}
运行:
nickName, changed from unknown name to 老猫
nickName, changed from 老猫 to 丁棍
3.3 可观察属性 - vetoable
vetoable
与 observable
一样,可以观察属性值的变化,不同的是,vetoable
可以通过处理函数来决定属性值是否生效。
来看这样一个例子:声明一个Int类型的属性age
,如果新的值比旧值大,则生效,否则不生效。
class Father {
var age: Int by Delegates.vetoable(0) { property, oldAge, newAge ->
println("Tips: ${property.name} has changed, from $oldAge change to $newAge")
newAge > oldAge // 如果新的值比旧的值大,则生效,否则则不生效
}
}
fun main() {
val father = Father()
father.age = 10
father.age = 20
father.age = 15
println("my father's age is: ${father.age}")
}
运行:
Tips: age has changed, from 0 change to 10
Tips: age has changed, from 10 change to 20
Tips: age has changed, from 20 change to 15
my father's age is: 20
可以看到,虽然对 age
的值做了3次修改,但是father.age = 15
这次的修改是不会生效的,因为这次赋的值小于之前赋的旧值。
3.4 属性之间的委托
从 Kotlin 1.4开始,一个属性可以把它的 getter
与 setter
委托给另一个属性,被委托的属性可以是顶层属性或普通类的属性均可。
那这个有什么作用呢?
比如我们对外提供了一个属性接口 mVersion
,用于获取当前固件的版本号,后来随着项目迭代我们觉得这样叫太笼统了,可能一个固件里面包含有很多其它子模块如 MCU, DSP 等等,以前的mVersion
接口可能当时只是用来表达MCU的version, 为了以后能够更方便的表达各个子模块的版本,我们计划使用 mMcuVersion
来替代mVersion
的作用,但是SDK已经给到客户了,已经有不少客户使用 mVersion
来获取MCU的版本号,为了前后兼容,可以使用kotlin提供的属性委托来解决。
例:
class Firmware {
@Deprecated("please use the 'mMcuVersion' to get the MCU version")
var mVersion: String by this::mMcuVersion // 双冒号前的this可省略
var mMcuVersion: String = "1.0.0"
}
fun main() {
val firmware = Firmware()
firmware.mMcuVersion = "2.0.0" // 我们今后只会更新 mMcuVersion的值
println("current MCU version: ${firmware.mVersion}") // 旧客户使用
println("current MCU version: ${firmware.mMcuVersion}") // 新客户使用
}
运行:
current MCU version: 2.0.0
current MCU version: 2.0.0
可以看到,我们将 mVersion
属性的 getter/setter 委托给了 mMcuVersion
属性,这样今后如果MCU的版本号需要更新,就不用再更新 mVersion
属性的值了,直接更新 mMcuVersion
的值即可,如果仍然有客户使用 mVersion
接口来获取MCU的版本,也没有关系,当读取 mVersion
的值时,会将 mMcuVersion
的值返回给他。
为将一个属性委托给另一个属性,可以使用 :: 限定符
,例如,this::delegate
或 MyClass::delegate
。
另外,我们还可以使用 @Deprecated
来注解旧的属性、这样当客户调用这个旧的属性时,会有中划线来表示过期,当鼠标移到上面时,会有警告信息。
3.5 属性存储于映射(map)中
还有一种情况,在一个映射(map)里存储属性的值,使用映射实例自身作为委托来实现委托属性,如:
class User(val info: Map<String, Any?>) {
val name: String by info
val age: Int by info
}
fun main() {
val user = User(
mapOf(
"name" to "ZhangSan",
"age" to 20
)
)
println("The user's name: ${user.name}, age: ${user.age}")
}
运行:
The user's name: ZhangSan, age: 20
使用映射实例自身作为委托来实现委托属性,可以使用在json解析中,因为json本身就可以解析成一个map。
<完>
参考链接