Kotlin 泛型和委托
泛型的基本用法
在一般的编程模式下,我们需要给任何一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性。
泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是 <T>
,使用任何英文字母或单词都可以,通常情况下 T 是一种约定俗成的泛型写法。
定义一个泛型类:
class MyClass<T> { |
定义一个泛型方法:
class MyClass{ |
由于 Kotlin 拥有出色的类型推导机制,这里可以直接省略泛型的指定:
val myClass = MyClass() |
Kotlin 还允许对泛型的类型进行限制,可以以通过指定上界的方式来对泛型的类型进行约束,如指定成数字类型:
class MyClass { |
默认情况下,所有的泛型都可以指定成可空类型,因为泛型的上界默认是 Any?
,如果想让泛型的类型不可为空,需要将泛型的上界指定为 Any
。
类委托和委托属性
委托是一种设计模式:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。Kotlin 中将委托功能分为了两种:类委托和委托属性。
类委托
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。
Set 是一个接口,使用它需要用它具体的实现类比如 HashSet。借助委托模式可以轻松实现一个自己的实现类,比如定义一个 MySet 并让它实现 Set 接口:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> { |
MySet 的构造函数中接收了一个 HashSet 参数,这就相当于一个辅助对象。Set 接口所有的实现方法中都没有进行自己的实现,而是调用了辅助对象中相应的方法实现,这其实就是一种委托模式。
如果只是让大部分的方法实现调用辅助对象中的方法,少部分的方法实现由自己重写,甚至加入一些自己独有的方法,那么 MySet 就会称为一个全新的数据结构类,这就是委托模式的意义所在。
Kotlin 中委托使用的关键字是 by
,只需要在接口声明的后面使用 by
关键字再接上受委托的辅助对象,就可以免去一大堆模板式代码:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet { |
上面两段代码效果一样,借助了类委托的功能后代码大大简化,如果要对某个方法重新实现只需要单独重写哪一个方法就可以了。
委托属性
委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。委托属性的语法结构如下:
class MyClass { |
这里使用 by
关键字连接左边的 p 属性和右边的 Delegate 实例,代表着将 p 属性的具体实现委托给 Delegate 类去完成。当调用 p 属性的时候会自动调用 Delegate 类的 getValue () 方法,当给 p 属性赋值的时候会自动调用 Delegate 类的 setValue () 方法。
class Delegate { |
这是一种标准的代码实现模板,在 Delagate 类中我门必须实现 getValue () 和 setValue () 这两个方法,并且都要使用 operator
关键字进行声明。
getValue () 方法要接收两个参数:第一个参数用于声明该 Delegate 类的委托功能可以在什么类中使用;第二个参数KProperty<*>
是 Kotlin 中的一个属性操作类,可用于获取各种属性相关的值,必须在方法参数上进行声明。<*> 这种泛型的写法表示不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似 Java 中 <?> 的写法。返回值可以声明成各种类型。
setValue () 方法接收 3 个参数,前两个和 getValue () 方法相同,最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和 getValue () 方法返回值的类型保持一致。
MyClass 中的 p 属性使用 val 关键字声明时,意味着 p 属性是无法在初始化之后杯重新赋值,因此没有必要实现 setValue () 方法,只需要实现 getValue () 方法就可以了。
实现一个自己的 lazy 函数
by lazy
代码块是 Kotlin 提供的一种懒加载技术,它的基本语法结构如下:
val p by lazy { ... } |
代码块中的代码一开始并不会执行,只有当 p 变量首次被调用的时候才执行,并且会将代码块中最后一行代码的返回值赋值给 p 变量。by 是 Kotlin 中的关键字,lazy 是一个高阶函数。在 lazy 函数中会创建并返回已给 Delegate 对象,调用 p 属性时其实调用的是 Delegate 对象的 getValue () 方法,然后 getValue () 方法又会调用 lazy 函数传入的 Lambda 表达式,这样表达式中的代码就可以得到执行了,并且调用 p 属性后得到的值就是 Lambda 表达式中最后一行代码的返回值。
class Later<T>(val block: () -> T) { |
这里定义了一个 Later 类并指定为泛型类,Later 的构造函数中接收一个函数类型参数,这个函数类型参数不接收任何参数,并且返回值类型就是 Later 类指定的泛型。getValue () 方法的第一个参数指定成 Any? 类型,表示我们希望 Later 的委托功能在所有类中都可以使用。然后使用了一个 value 变量对值进行缓存,如果 value 值为空就调用构造函数中传入的函数类型参数去获取值,否则就直接返回。懒加载技术不会对属性机型赋值,因此这里不用实现 setValue () 方法。
为了让用法更类似 lazy 函数,可以再定义一个顶层函数:
fun <T> later(block: () -> T) = Later(block) |
这里将顶层函数也定义成了泛型函数,并接收一个函数类型参数,这个顶层函数的作用很简单:创建 Later 类的实例,并将接收的函数类型参数传给 Later 类的构造函数。现在可以用 later 简单替代 lazy 函数了:
val p by later { ... } |