泛型的基本用法

泛型,即 “参数化类型”,将类型参数化,可以用在类,接口,方法上。与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。

泛型主要有两种定义方式,一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下,T是一种约定俗成的泛型写法。

如果要定义一个泛型类,就可以这么写:

class MyClass<T> {
    fun method(param: T): T {
        return param
    } 
}

在调用MyClass类和method()方法的时候,可以将泛型指定成具体的类型,如下所示:

val myClass = MyClass<Int>()
val result = myClass.method(123)

而如果不想定义一个泛型类,只是想定义一个泛型方法,只需要将定义泛型的语法结构写在方法上面就可以了,如下所示:

class MyClass {
    fun <T> method(param: T): T {
        return param
    }
}

此时的调用方式也需要进行相应的调整:

val myClass = MyClass()
val result = myClass.method<Int>(123)

可以看到,现在是在调用method()方法的时候指定泛型类型了。另外,Kotlin还拥有非常出色的类型推导机制,例如传入了一个Int类型的参数,它能够自动推导出泛型的类型就是Int型,因此这里也可以直接省略泛型的指定:

val myClass = MyClass()
val result = myClass.method(123)

泛型约束

我们可以使用泛型约束来设定一个给定参数允许使用的类型。

Kotlin 中使用 : 对泛型的类型上限进行约束。

最常见的约束是上界(upper bound):

fun <T : Comparable<T>> sort(list: List<T>) {
    // ……
}

Comparable 的子类型可以替代 T。 例如:

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型

默认的上界是 Any?。

对于多个上界约束条件,可以用 where 子句:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

类委托

委托模式的基本理念是操作对象不会自己去处理某段逻辑,而是会把工作委托给另一个辅助对象去处理。

我们举个例子,Set是一个接口,它所存储的数据是无序且不可重复的,如果要使用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()
}

可以看到,就是接受了一个HashSet参数,相当于一个辅助对象,直接使用辅助对象的方法,这就是委托模式,但是委托也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个方法的话,每个都去这样调用辅助对象中的相应方法实现,写起了就非常复杂了。这个问题在Kotlin中可以通过类委托的功能来解决。

类委托使用关键字by,再接受受委托的辅助对象

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
   
}

另外,如果我们要对某个方法进行重新实现,只需要单独重写那个方法就行了,当然也可以新增方法

class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
    fun helloWorld() = println("Hello World")
    override fun isEmpty() = false
}

委托属性

委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。

我们看一下委托属性的语法结构,如下所示:

class MyClass {
    var p by Delegate()
}

这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了的Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

因此,我们还得对Delegate类进行具体的实现才行,代码如下所示:

class Delegate {
    var propValue: Any? = null
    operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
        return propValue
    }

    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
        propValue = value
    }
}

整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用Delegate类的setValue()方法,当获取MyClass中p属性的值时,就会调用Delegate类的getValue()方法。

其中getValue的参数中,第一个是声明委托属性可以在什么类中使用,第二个参数KProperty<>是Kotlin中的一个属性操作集,可以用于获取各种属性相关的值,"<>"表示不知道或者不关心泛型的具体类型

对于

val p by lazy{ ... }

其实就是委托属性,lazy是一个高阶函数,再lazy中会创建并返回一个Delegate对象,当我们调用p属性时,其实调用的时Delegate对象的getValue()方法,然后getValue()方法又会调用传入lazy中的Lambda表达式,并且调用p属性得到的值就是Lambda表达式中最后一行代码的返回值。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐