一、泛型的定义
1
2
3
4
5
6
7
8
9
10
11
class Box<T>(t: T) {
var value = t
}
//使用时提供类型参数
val box: Box<Int> = Box<Int>(1)
//如果类型是可推导的,也可以省略类型参数
val box = Box(1) // 1 has type Int, so the compiler figures out that it is Box<Int>
二、关键字
1. out
简单来说就是out
被用在T的生产者,而不是消费者,类似于java 的 ? extends
1
2
3
4
5
6
7
8
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
2. in
简单来说就是in
被用在T的消费者,而不是生产者。类似于java中的? super
1
2
3
4
5
6
7
8
9
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, you can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK!
}
3. where
当我们的泛型需要多个上界时,使用where
进行指定,例如我们希望我们的泛型类型同时满足实现多个接口。
在java中通过&
符号进行多个边界,Kotlin中使用where
进行指定,效果是一致的。
1
class Writer<T> where T : Closeable, T : Comparable<T>
4. 下划线(_)
_
可以当作类型参数,当显式的指定来其他类型时,可以使用_
进行自动推导
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
abstract class SomeClass<T> {
abstract fun execute() : T
}
class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}
object Runner {
inline fun <reified S: SomeClass<T>, T> run() : T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T is inferred as String because SomeImplementation derives from SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// T is inferred as Int because OtherImplementation derives from SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
5. *号
泛型的一个通配符,通常情况等价于 out Any
,但如果类型中已经指定了型变及类型,则还是和原始类型一样,如类型定义为out T:Number
则加上<*>
效果将是 out Number
6. reified
由于类型擦除的原因,在运行时无法知道泛型的确切类型,可以使用reified
关键字让泛型信息保留下来
1
2
3
4
5
6
7
8
9
10
11
12
13
//我们无法检查类型,同时编译器也会给出错误提示
fun <T> printType(item: Any) {
if (item is T) { // Cannot check for instance of erased type: T
println(item)
}
}
//在java中通常会多传递一个类型进去进行检查,Kotlin中提供了更便捷的方法
inline fun <reified T> printType(item: Any) {
if (item is T) {
println(item)
}
}
需要注意
reified
只能在内联函数中使用,仅支持类和接口使用,不能用于属性
三、型变
1. 协变(covariance)
例如:Int
是Number
的子类,那应该List<Int>赋值给List<Number>
是可行的,但编译器并不允许这样做,,可以通过out
来修饰,让他具有协变性。通常是只读不可写,也就像上面说的只生产,不消费。
2. 逆变(contravariance)
例如:Int
是Number
的子类,我们希望将List
3. 不变(invariant)
泛型默认是不变的,因为如果是默认可变的可能会出现一些无法控制的问题,例如下面,果是可变的可能出现很多不易察觉的问题。
1
2
3
4
5
val float:Float = 1f
val int :MutableList<Int> = mutableListOf<Int>()
val numbers:MutableList<Number> = int //由于不可变性,这里是无法直接赋值的
numbers.add(float)
val int0 = numbers[0]
四、泛型函数
不仅类可以有类型参数,函数也可以,函数的类型参数需要放在函数名前面
1
2
3
4
5
6
7
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString(): String { // extension function
// ...
}
泛型函数的调用
1
2
3
val l = singletonList<Int>(1)
//同样如果是可推导的情况可以省略类型
val l = singletonList(1)
五、泛型约束
最常见的约束就是类型的上界,对应java 泛型中的 extends
1
fun <T : Comparable<T>> sort(list: List<T>) { ... }
默认的上界是Any?
,括号内只能指定一个上界,如果需要存在多个上界就需要使用where
关键字了
1
2
3
4
5
6
//表示T需要既实现CharSequence接口 也要实现 Comparable接口
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
六、类型擦除
Kotlin 的泛型在运行时被擦除,这意味着在运行时无法获取泛型的具体类型参数。这与 Java 不同,Java 的泛型在运行时保留类型信息。例如,Foo<Bar>
与 Foo<Baz?>
的实例都会被擦除为 Foo<*>
由于类型擦除,你无法直接使用is
来进行类型检查
1
2
3
4
5
6
7
8
//这样是错误的
if (something is List<Int>){
}
//可以使用通配符*来校验
if (something is List<*>){
}
七、泛型的绝对非空
可能会说,类型不是已经可以通过?
来声明是否为空了吗,但由于泛型比较特殊,比如,如果上界是可空的,那么T
或T?
任然是可空的,这种场景下可能就会感觉?
的可空声明为什么怎么混乱。
1
2
3
4
5
6
7
8
9
10
fun <T : Number?> create(t1: T, t2: T?) {
t1.toInt() // call is not allowed, `t1` may be null
t2.toInt() // call is not allowed, `t2` may be null
}
fun <T : Number> create(t1: T, t2: T?) {
t1.toInt() // call is allowed, `t1` may not be null
t2.toInt() // call is not allowed, `t2` may be null
}
Kotlin中又引入了一个绝对非空的概念,例如下面这个场景
1
2
3
4
5
6
fun <T> ifNullUseDefault(x: T, y: T & Any): T & Any = x ?: y
//error
ifNullUseDefault("",null)
//ok
ifNullUseDefault("","")