引言:和Java不一样,
Kotlin
始终要求类型实参要么被显式地说明,要么能被编译器推导出来。例如,在Java中,能够声明List类型的变量,而不须要说明它能够包含哪类事物。而Kotlin
从一开始就有泛型,因此它**不支持
没有类型参数的泛型类**(即原生态类型),类型实参必须定义
。markdown
泛型函数:编写一个使用列表的函数,要求在任何列表(通用的列表)上使用,而不是某个具体类型的元素的列表,这个函数即为泛型函数。app
泛型函数有它本身的类型形参。这些形参在每次函数调用时都必须替换成具体的类型实参。ide
大部分使用集合的库函数都是泛型的。来看看图1中的slice函数。这个函数返回一个只包含在指定下标区间内的元素。函数
接受者和返回类型用到了函数的类型形参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
和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
类型参数约束能够限制做为(泛型)类和(泛型)函数的类型实参的类型。
以计算列表元素之和的函数为例。它能够用在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.
复制代码
这种状况下,能够说明做为类型实参的类型必须实现**CharSequence
和Apppendable
两个接口**。这意味着该类型的值能够使用访问数据(endsWith
)和修改数据(append
)两个操做。
若声明的是泛型类或者泛型函数,任何类型实参,包括那些可空的类型实参,均可以替换他的形参类型。
事实上,没有指定上界的类型形参将会使用**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
永远都是非空类型!