Kotlin学习(一)—Kotlin的泛型

416 阅读5分钟

前言

在Kotlin中使用泛型的方式跟Java大体类似,其中也有一些特性的差别。不论是Java中的泛型还是Kotlin中的泛型,总有些概念会让人产生困惑。接下来回结合Java的泛型来学习Kotlin的泛型。

Kotlin中泛型的使用

在泛型的使用中,最常用到的就是泛型函数以及泛型类。在介绍这两种方式的使用之前,先介绍一下泛型的类型参数。

泛型类型参数

泛型的类型参数就是在声明泛型时定义的类型形参,表明了在使用泛型时定义的一种类型。在泛型实例化是类型形参就被替换成了类型实参。比如:List  实例化为   List时,类型形参T就被替换成了类型实参String。

由于泛型是在Java 1.5时引入Java的,为了兼容老版本,在Java中允许使用没有类型形参的泛型。比如:List list = new ArrayList();这种生命是允许存在的。但是在Kotlin中泛型声明必须声明类型形参或者能够推导出类型形参。比如:val list = listof("a", "b");类型形参被推倒为String类型或者List直接声明为String类型。

泛型函数以及泛型类

//泛型函数
fun <T> someFunc(item: T): List<T> {
    // do something
}
//泛型类
class Hello<T> {
    val value: T
}

可以看到,在kotlin中使用泛型函数跟泛型类的方式与Java大致相同。除此之外,Kotlin还支持为一个类型制定多个约束的特性。

参数类型约束

在Kotlin中可以为类型参数指定约束,比如:fun  sum() T,这个泛型函数指定了类型参数的上限为Number类型,那个类型参数只能是Number或者Number的子类。这个在Java中的表示是extends实现,即:

处了上面的约束外,Kotlin还可以指定多个约束。

fun <T> someFunc(value: T) where T : CharSequence, T : Appendable {
	if (!value.endsWith('.')) {
		value.append('.')
	}
}

上面这个泛型函数就指定了多个类型参数约束,在上面的情况下,类型参数必须实现CharSequence和Appendable两个接口。

类型参数的空与非空:在Kotlin中每个类型都有空与非空两种情况,实现泛型时也是如此。比如:List<String?>声明类型参数为一个可空的String类型,而List声明类型参数为不可空的String类型。

运行时的泛型

和Java一样,在运行时Kotlin的类型信息也会被擦除,类型擦除随之带来的就是类型的消失。

类型擦除在Kotlin中的影响

  • 函数参数受到类型擦除的影响,比如:fun func1(list: List); fun func2(list: List);这两个使用List泛型,受到类型擦除的影响,在运行时,无法分辨两个函数参数的差异;
  • 类型检查,在类型擦书后,运行时无法检查对应的类型。比如 value is List,在Kotlin中的替代方式是value is List<*>,利用星号投影检查。

实化类型参数

实化类型参数的意思就是可以使用类型参数。这个特性是Kotlin中加入的,通常是在内联函数

inline fun <reified T> isA(value: Any) = value is T

可以看到在内联函数中可以直接使用类型参数T。这样的代码在普通函数中是不能正常编译的(会爬出异常Error: Cannot check for instance of erased type: T)。可以在内联函数中使用是因为内联函数直接以字节码的形式插入到调用的地方,所以在运行时可以获取到类型实参。还有就是reified关键字的作用就是声明了类型参数在运行时不被擦除。

Kotlin中的型变

不变型

Kotlin的不变型指的是:例如,对于两种任意类型A和B,MutableList既不是MutableList的子类型也不是超类型,那么成为在该类型参数上是不变型的(Java中所有的类型都是不变型的)。

协变

在Kotlin中对于一个泛型类,如果A是B的子类型, SomeClass就是SomeClass的子类型,也就是说子类型被保留了,这样就是协变。

//类被声明成在T上协变
class SomeClass<out T> {
	fun someFinc(): T
}

上面的代码中就是声明协变的方式,即加入out关键字。协变只能生产类型T而不能消耗类型T,比如:只能get不能set,也就是协变是只读的。

在Java中与之对应的是<? extends T>的上界通配符。

逆变

在Kotlin中逆变可以看做是协变的镜像,下面是声明逆变的方式。

//声明逆变类型
interface Comparator<in T> {
	fun compare(e1: T, e2: T): Int {} //声明逆变使用in关键字
}

逆变可以写入,但是返回只能是基础类型。

在Java与之对应的是<? super T>下界通配符。

星号投影

而在 Kotlin 中,在参数类型未知时,可以用星投影来安全的使用泛型。但是MutableList<*>与MutableList<Any?>是不一样的。MutableList< * >相当于MutableList<out Any?>,是只读的。因为不明确具体类型的情况下,读是安全的,写是不安全的。

在Java中与之对应的是<?>无界通配符。

总结

Kotlin中的泛型与Java是相同的,处了几个Kotlin新加入的特性(比如:多约束类型,实化参数等),其他都与Java相通。

以上内容并没有讲明更多的细节,只是列出了Kotlin中泛型的内容以及与Java泛型的联系。感兴趣的同学可以继续深入学习。