首页 Kotlin学习笔记(15) - 泛型
文章
取消

Kotlin学习笔记(15) - 泛型

一、泛型的定义

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)

例如:IntNumber的子类,那应该List<Int>赋值给List<Number>是可行的,但编译器并不允许这样做,,可以通过out来修饰,让他具有协变性。通常是只读不可写,也就像上面说的只生产,不消费。

2. 逆变(contravariance)

例如:IntNumber的子类,我们希望将List赋值给 `List`,由于不可变性,这也是不被允许的,但可以通过`in`来修饰,就具备了逆变性。通常是只写不读,也就像上面说的只消费,不生产。

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<*>){

}

七、泛型的绝对非空

可能会说,类型不是已经可以通过?来声明是否为空了吗,但由于泛型比较特殊,比如,如果上界是可空的,那么TT?任然是可空的,这种场景下可能就会感觉?的可空声明为什么怎么混乱。

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("","")
本文由作者按照 CC BY 4.0 进行授权

Kotlin学习笔记(14) - 类型别名

Kotlin学习笔记(16) - 函数的基础定义