本篇文章主要介绍以下几个知识点:
- 泛型的基本用法
- 类委托和委托属性
- 自定义 lazy 函数
内容参考自第一行代码第3版
1. 泛型的基本用法
泛型允许在不指定具体类型的情况下进行编程,这样会有更好的扩展性。
泛型主要有两种定义方式:定义泛型类 和 定义泛型方法。使用的语法结构都是<T>
,当然括号内的 T 可以用任何英文字母或单词。
如定义个泛型类:
class MyClass<T> {
fun method(param: T): T {
return param
}
}
在调用 MyClass
类和 method()
方法时就可以将泛型指定成具体的类型:
fun main() {
// 这边把泛型指定成 Int 类型
val mClass = MyClass<Int>()
val result = mClass.method(123)
println(result)
}
若不想定义泛型类,只想定义一个泛型方法,可改为:
class MyClass {
fun <T> method(param: T): T {
return param
}
}
此时调用方式也要修改:
fun main() {
val mClass = MyClass()
// val result = mClass.method<Int>(123)
// 由于 Kotlin 的推导机制,这边的 Int 可以省略
val result = mClass.method(123)
println(result)
}
Kotlin 还允许对泛型类型进行限制,通过指定上界的方式对泛型的类型进行约束,如将设置为 Number 类型:
class MyClass {
fun <T : Number> method(param: T): T {
return param
}
}
注:在默认情况下,泛型的上界是 Any?
,即所有的泛型都是可以指定成可空类型的,若想让泛型的类型不可空,手动指定成 Any
即可。
回顾前面学习高阶函数时的一个例子:
// 这个 build 函数和 apply 函数的作用是一样的,
// 但 build 函数只能作用在 StringBuilder 中,而 apply 可以作用在所有类上
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
block()
return this
}
要让上面的 build
实现和 apply
一样的功能,可使用泛型修改如下:
fun <T> T.build(block: T.() -> Unit): T {
block()
return this
}
这样就可以和 apply
一样去使用 build
函数了。
2. 类委托和委托属性
委托是一种设计模式,其基本理念是:操作对象自己不会去处理某段逻辑,而是把工作委托给另外一个辅助对象去处理。
Kotlin 中将委托功能分为:类委托和委托属性。(注:Java 对委托没有语言层面的实现)
- 类委托:将一个类的具体实现委托给另一个类去完成。
下面举个栗子,定义一个 MySet
类实现 Set
接口:
// Set 数据结构和 list 不同的是所存储的数据是无序的,不能存储重复的数据
// Set 接口,要使用它需要使用它具体的实现类,如 HashSet
// 这边在构造函数中接收了一个 HashSet 参数,相当于一个辅助对象
// 然后在 Set 接口中所有的方法实现中,都没有进行自己的实现,
// 而是调用了辅助对象 HashSet 中相应的方法,这其实就是一种委托模式
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int get() = helperSet.size
override fun contains(element: T) = helperSet.contains(element)
override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
override fun isEmpty() = helperSet.isEmpty()
override fun iterator() = helperSet.iterator()
}
委托模式的意义主要在于:大部分的方法调用辅助对象中的方法,少部分的方法由自己实现,甚至加入一些独有方法。(如果都调用辅助对象中的方法那还不如直接使用辅助对象得了)
不过,上面的写法有一定的弊端,当辅助对象中存在很多方法时,每个都去这样调用辅助对象中的相应方法实现,那就很繁琐了。还好 Kotlin 中可以通过类委托来解决。
Kotlin 中委托使用的关键字是 by
,在接口声明的后面使用 by
关键字,再接上受委托的辅助对象即可:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{
// 通过类委托,这边可以免去之前的一大堆模板式代码了,实现的效果却是一样的
}
比如对其某个方法重新实现、新增自己的方法:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
// 新增一个 sayHello 方法
fun sayHello() = println("Hello Wonderful")
// 重写 isEmpty 方法
override fun isEmpty() = false
}
- 委托属性:将一个属性(字段)的具体实现委托给另一个类去完成。
委托属性的语法结构如下:
class MyClass {
// by 关键字左边 p 属性的具体实现委托给了 Delegate 类去完成
var p by Delegate()
}
接着还得对 Delegate 类进行具体的实现,如下:
class Delegate {
var propValue: Any? = null
// 需要使用 operator 关键字声明
// KProperty<*> 是 Kotlin 中的一个属性操作类,可用于获取各种属性相关的值,
// 即使用不着也必须在方法参数上声明
// <*> 表示不关心泛型的具体类型(类似于 Java 中的<?>)
operator fun getValue(mClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(mClass: MyClass, prop: KProperty<*>, value: Any?) {
propValue = value
}
}
这样当调用 p 属性时会自动调用 Delegate 类的 getValue()
方法,当给 p 属性赋值时会自动调用 Delegate 类的 setValue()
方法。
3. 实现一个自己的 lazy 函数
有时候初始化变量时,会用一种懒加载技术,把想要延迟执行的代码放到 by lazy
代码块中,这样代码块中的代码在一开始时就不会执行,只有当变量首次被调用时代码块中的代码才会执行。
by lazy
的语法结构如下:
// 这里 by lazy 不是连在一起的,by 是关键字,lazy 是一个高阶函数
// 在 lazy 函数中会创建并返回一个 Delegate 对象
val p by lazy { ... }
下面举个栗子来实现一个自己的 lazy
函数:
class Later<T>(val block: () -> T) {
// 这里定义了个 Later 类,并将他指定成泛型类
}
接着在 Later
类中实现 getValue()
方法:
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
// 由于懒加载技术不会对属性进行赋值,无需实现 setValue 方法
}
为了让它用法更加类似于 lazy 函数,再定义个顶层函数:
// 用于创建 Later 类的实例,并将接收的函数类型参数传给 Later 类的构造函数
fun <T> later(block: () -> T) = Later(block)
这样,自定义的 later 懒加载函数就完成了,用法如下:
val p by later {
// something to do
}
本篇文章就介绍到这。