Kotlin 的泛型是指带类型形参的类型。当泛型的实例被创建时,类型形参被替换为具体类型。 ?
以 Kotlin 的 list 方法为例,当创建一个字符串列表时,类型形参 T 被替换为具体类型 String。
val strList: List<String>
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)
除了 List,还可以给一个类型声明多个类型形参,比如 Map 有 Key 和 Value 两个类型形参。
val map : Map<String, Person>
public interface Map<K, out V>
和 Kotlin 的普通类型一样,泛型也支持类型推导。
val authors = listOf("Dmitry", "Svetlana")
authors 没有显示指明它是字符串列表,但是因为 list 里面有两个字符串,所以被推导为字符串列表类型。 ?
但是如果是一个空列表,就必须指定类型参数。因为编译器也不知道列表里面是什么类型的值,无法推导泛型的类型参数。
val readers: List<String> = mutableListOf()
也可以在 mutableListOf 指定类型实参。
val readers = mutableListOf<String>()
Kotlin 的泛型类型实参和 Java 的类型实参有一些区别。
Kotlin 必须显式指定类型实参或者可以被推导出类型实参。而 Java 可以不指定类型实参。 ?
Java 可以这么写:
List list = new ArrayList();
但是 Kotlin 不行。这是因为 Java 的泛型是 Java 1.5 版本引入的特性,为了兼容老版本,它必须可以写成不带类型实参的形式。而 Kotlin 从一开始就设计了泛型,必须定义类型实参。
泛型函数和属性
泛型函数
大部分使用集合的库函数都是泛型的。比如 slice。
public fun <T> List<T>.slice(indices: IntRange): List<T> {
if (indices.isEmpty()) return listOf()
return this.subList(indices.start, indices.endInclusive + 1).toList()
}
fun 后面的 表示类型形参声明。 ?
List 表示接收者是 List 类型,一个由 T 类型组成的列表。slice 函数是这个 List 类型的成员函数或者扩展函数。 ?
: List 表示 slice 的返回值也是一个由 T 类型组成的列表。 ?
调用泛型函数可以显式指定类型实参,也可以完全不写类型实参,由编译器推导类型。
val letters = ('a'..'z').toList()
println(letters.slice<Char>(0..2))
println(letters.slice(10..13))
泛化的高阶函数
泛化的高阶函数是指函数参数为泛型函数的高阶函数。 ?
filter 方法接收一个 (T) -> Boolean 类型的 predicate 函数,它的类型形参和接收者的类型形参相同。
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
?
调用 List 的 filter 方法。编译器推导出 it 的类型是 String,和接收者的类型参数相同。
private fun filter() {
val authors = listOf("Dmitry", "Svetlana")
val readers: List<String> = mutableListOf("Dmitry", "Svetlana", "zhangsan", "lisi")
readers.filter {
it !in authors
}.apply { println(this) }
}
泛型属性
和泛型函数一样,还可以用相同的语法声明泛型的扩展属性。 ?
比如返回列表的倒数第二个元素。
val <T> List<T>.penultimate : T
get() = this[size - 2]
?
不能声明泛型非扩展属性。因为普通属性不能存储多个不同类型的值,只有带接收者定义的扩展属性才有意义。 ?
以下代码编译器会报错,属性的类型参数只能用在它的接收者类型。也就是扩展属性。
val <T> x: T = 1
声明泛型类
泛型类使用 <> 操作符定义类型参数。 ?
List 接口定义了 T 类型的类型参数,T 类型可以作为 get 方法的返回值类型。
interface List<T> {
operator fun get(index: Int): T
}
继承泛型类
继承泛型类有 2 种方法:
- 指定具体类型
- 继续使用类型参数
指定具体类型
class StringList : List<String> {
override fun get(index: Int): String {
TODO("Not yet implemented")
}
}
继续使用类型参数
class ArrayList<T> : List<T> {
override fun get(index: Int): T {
TODO("Not yet implemented")
}
}
继承类自身也可以是泛型的类型参数。 ?
比如 String 类继承了 Comparable 接口,同时它也是 Comparable 的类型参数 。
interface Comparable<T> {
fun compareTo(other: T): Int
}
class String : Comparable<String> {
override fun compareTo(other: String): Int {
TODO("Not yet implemented")
}
}
类型参数约束
类型参数约束可以限制泛型的参数类型。比如对 List 的所有元素求和,可以对 List 或者 List 求和,但是无法对 List 求和。所以求和函数必须限制 List 的类型参数。
类型参数的上界
Kotlin 使用 : 限定类型参数的上界。与 Java 的 extend 类似。
fun <T : Number> List<T>.sum(): T
sum 方法的类型参数必需是 Number 的子类。 ?
当类型参数的上界被指定后,可以调用上界的方法。 ?
比如 oneHalf 调用了 Number 的 toDouble。
fun <T : Number> oneHalf(num: T): Double {
return num.toDouble() / 2.0
}
fun main() {
println(oneHalf(3))
}
max 函数比较了两个值,因此它的类型参数必须实现了 Comparable 接口,上界为 Comparable。 ?
String 类型实现了 Comparable,因此可以比较 “kotlin”, “java”。
fun <T : Comparable<T>> max(first: T, second: T): T {
return if (first > second) {
first
} else {
second
}
}
fun main() {
println(max("kotlin", "java"))
}
类型参数的多重约束
泛型的类型参数可以设置多个约束。 ?
比如类型参数 T 既继承了 CharSequence,也继承了 Appendable。这样它可以同时调用 endsWith 和 append。 endsWith 是 CharSequence 的方法,append 是 Appendable 的方法。
fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
}
fun main() {
val helloWorld = StringBuilder("Hello World")
ensureTrailingPeriod(helloWorld)
println(helloWorld)
}
让类型参数非空
泛型的类型参数可以为空类型。有时候需要约束泛型类型不为空,保证参数为非空类型。 ?
在 process 内部使用 ?. 安全调用,保证 arg 为空时打印 “null arg”。
class Processor<T> {
fun process(arg : T) {
arg?.hashCode() ?: println("null arg")
}
}
fun main() {
val nullableProcessor = Processor<String?>()
nullableProcessor.process(null)
}
更好的方法是指定类型参数的上界为 Any 类型。Any 类型是所有非空类型的父类。
class Processor<T : Any> {
fun process(arg: T) {
arg.hashCode()
}
}
fun main() {
val nullableProcessor = Processor<String?>()
nullableProcessor.process(null)
}
Processor 这里也可以不用 Any,只要是任何非空类型作为上界就行。 ?
题外话:这里的示例直接调用 hashCode 也不会报错。因为 hashCode 也是一个扩展方法。
class Processor<T> {
fun process(arg: T) {
arg.hashCode()
}
}
fun main() {
val nullableProcessor = Processor<String?>()
nullableProcessor.process(null)
}
hashCode 是 Any? 的扩展方法,如果接收者为空,直接返回 0。 ?
HashCode.kt
package kotlin
import kotlin.internal.InlineOnly
@SinceKotlin("1.3")
@InlineOnly
public inline fun Any?.hashCode(): Int = this?.hashCode() ?: 0
|