Kotlin进阶知识(九)——泛型类型参数

引言:和Java不一样,Kotlin始终要求类型实参要么被显式地说明,要么能被编译器推导出来。例如,在Java中,能够声明List类型的变量,而不须要说明它能够包含哪类事物。而Kotlin从一开始就有泛型,因此它**不支持没有类型参数的泛型类**(即原生态类型),类型实参必须定义markdown

1、泛型函数和属性

泛型函数:编写一个使用列表的函数,要求在任何列表(通用的列表)上使用,而不是某个具体类型的元素的列表,这个函数即为泛型函数。app

泛型函数有它本身的类型形参。这些形参每次函数调用时都必须替换成具体类型实参ide

大部分使用集合的库函数都是泛型的。来看看图1中的slice函数。这个函数返回一个只包含在指定下标区间内的元素。函数

图1:slice泛型函数的类型形参为T

接受者和返回类型用到了函数的类型形参T,它们的类型都是List<T>。在一个具体的列表上调用这个函数时,能够显式地指定类型实参。但大部分状况下无需声明,由于编译器会推导出类型。性能

  • 调用泛型函数
fun main(args: Array<String>) {
    genericFunctionTest()
}

fun genericFunctionTest() {
    val letters = ('a' .. 'z').toList()

    // 显式地指定类型实参
    println(letters.slice<Char>(0 .. 2))

    // 编译器推导出这里的T是Char
    println(letters.slice(10 .. 14))
}

// 输出结果
[a, b, c]
[k, l, m, n]
复制代码
  • 声明泛型的扩展属性
// 这个泛型扩展属性能在任何种类元素的列表上调用
val <T> List<T>.penultimate: T
    get() = this[size - 2]

// 在此次调用汇总,类型参数T被推导成Int
>>> println(listOf(1, 2, 3, 4).penultimate)
3
复制代码

不能声明泛型非扩展属性 普通(即非扩展)属性不能拥有类型参数,不能在一个类的属性中存储多个不一样类型的值。ui

2、声明泛型类

和Java同样,Kotlin经过在类名称后加上一对尖括号,并把类型参数放在尖括号内来声明泛型类泛型接口。一旦声明以后,就能够在类的主体内像其余类型同样使用类型参数。this

// List接口定义了类型参数T
interface List<T> {
    // 在接口或类的内部,T能够看成普通类型使用
    operator fun get(index: Int): T
    // ...
}
复制代码

若是自定义的类继承了泛型类(或者实现了泛型接口),就得为基础类型的泛型形参提供一个类型实参。它能够是具体类型或者另外一个类型形参:spa

// 这个类实现了List,提供了具体类型实参:String
class StringList: List<String> {
    // 注意T如何被String代替
    override fun get(index: Int): String = ... }

// 如今ArrayList的泛型类型形参T就是List的类型实参
class ArrayList: List<T> {
    override fun get(index: Int): T = ... }
复制代码

StringList类被声明成只能包含String元素,因此它使用String做为基本类型的类型实参。code

ArrayList类定义了它本身的类型参数T并把它指定为父类的类型实参。orm

3、类型参数约束

类型参数约束能够限制做为(泛型)类和(泛型)函数的类型实参的类型。

以计算列表元素之和的函数为例。它能够用在List和List上,但不能够用在List这样的列表上。能够定义一个类型参数约束,说明sum类型形参必须是数字,来表达这个限制。

上界约束:在泛型类型具体的初始化中,其对应的类型实参必须是这个具体类型或者它的子类型

定义:把冒号放在类型参数名称 以后,做为类型形参上界的类型紧随**其后**,以下:

// Java 
<T extends Number> T sum(List<T> list)

// Kotlin
fun <T: Number> List<T>.sum(): T
复制代码

一旦指定了类型形参T的上界,就能够把类型T的值看成它的上界(类型)的值使用。例如:能够调用定义在上界类的方法:

// 指定Number为类型形参的上界
fun <T: Number> oneHalf(value: T): Double {
    // 调用Number类中的方法
    return value.toDouble() / 2
}

fun oneHalfTest() {
    println(oneHalf(100))
}
复制代码
  • 为一个类型参数指定多个约束
fun <T> ensureTrailingPeriod(seq: T)
        // 类型参数约束的列表
        where T: CharSequence, T: Appendable {
    // 调用为CharSequence接口定义的扩展函数
    if(!seq.endsWith('.')) {
        // 调用Appendable接口的方法
        seq.append('.')
    }
}

fun ensureTrailingPeriodTest() {
    val helloWorld = StringBuilder("Hello World")
    ensureTrailingPeriod(helloWorld)
    println(helloWorld)
}

// 输出结果
Hello World.
复制代码

这种状况下,能够说明做为类型实参的类型必须实现**CharSequenceApppendable两个接口**。这意味着该类型的值能够使用访问数据endsWith)和修改数据append)两个操做。

4、让类型形参非空

若声明的是泛型类或者泛型函数,任何类型实参,包括那些可空的类型实参,均可以替换他的形参类型。

事实上,没有指定上界类型形参将会使用**Any?这个默认的上界**。

class Processor<T> {
    fun process(value: T) {
        value?.hashCode()
    }
}
复制代码

process 函数中,参数value是可空的,尽管T没有使用问号标记

若想保证替换类型形参始终是非空类型,能够经过指定一个约束来实现。若除了可空性以外没有任何限制,能够使用**Any代替默认的Any?做为上界**:

// 指定非“空”上界
class ProcessorNew<T: Any> {
    fun process(value: T) {
        // 类型T的值如今是非“空”的
        value.hashCode()
    }
}
复制代码

约束<T: Any>确保了类型T永远都是非空类型

相关文章
相关标签/搜索