前言
什么是泛型?在我们一般的编程模式下,我们需要给任何一个变量指定一个具体的数据类型,而泛型允许我们不指定具体类型的情况下进行编程,这样会具有更好的扩展性
泛型的基本用法
泛型主要有两种定义方式:定义泛型类,及定义泛型方法,使用的语法结构都是,括号中的字母使用任何字母都可以的,T只是常规写法
在Kotlin中,还拥有非常出色的类型推导机制,假设我们传入一个Int类型的参数,Kotlin能够自动推导出泛型的类型就是Int型
object BaseGeneric {
@JvmStatic
fun main(array: Array<String>) {
val myClass = MyClass<String>()
println(myClass.method("123"))
val myClass2 = MyClass2()
println(myClass2.method(null))
println(myClass2.method2(789))
println(myClass2.method3(456))
}
}
// 定义泛型类
class MyClass<T> {
fun method(param: T): T {
return param
}
}
// 定义泛型方法
class MyClass2 {
// 泛型的上界默认是 Any?,可传空
fun <T> method(param: T): T {
return param
}
// 泛型的上界为Number类型,指定为数字类型
fun <T: Number> method2(param: T): T {
return param
}
// 为了指定泛型不可空,可指定泛型的上界默认 Any,不可传空
fun <T: Any> method3(param: T): T {
return param
}
}
泛型的高级特性
对泛型进行实化
Java的泛型功能是通过类型擦除机制来实现的,也就是说泛型对于类型的约束只在编译时期存在,在运行时期仍然会按照JDK 1.5之前的机制来运行,JVM识别不出我们在代码中指定的泛型类型
所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,Kotlin也包括在内;
但Kotlin提供了一个新的特性,泛型实化,但怎样实现泛型实化呢?首先该函数必须是内联函数,也就是用inline修饰的函数,其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行实化
inline fun <reified T> getGenericType() = T::class.java
因此,要实现泛型实化,必须满足两个条件:内联函数和reified关键字修饰
泛型实化的应用
在写Android代码的时候,经常会涉及到Activity的启动,通过启动一个新的Activity的时候,我们需要写该Activity::class.java,因此,我们可以借助泛型的实化来优化这种代码:
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
泛型的协变
一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以被称为in位置;而它的返回值是输出数据的地方,可以成为out位置
interface MyClass<T> {
fun method(param: T) : T {
// 在这个函数中,传参的T是in位置,返回值的T是out位置
}
}
在Java中,假设Student类是Person类的子类,那么SimpleDate(Student)并不是SimpleDate(Person)的子类
在Kotlin中,泛型的协变的定义:假如定义了一个MyClass的泛型类,其实A是B的子类型,同时MyClass<A>又是MyClass<B>的子类型,那么就可以称在T这个泛型上是协变的
那么如何才能让MyClass<A>是MyClass<B>的子类型呢?如果一个泛型在其泛型的数据上是只读的,那么它是没有类型转换安全隐患的,而要实现这一点,则需要让MyClass类中的所有方法都不能接收T类型的参数,也就是说,T只能出现在out位置上,而不能出现在in位置上
class SimpleData<out T>(val data: T?) {
fun get(): T? {
return data
}
fun main() {
val student = Student("kotlin", 19)
val data = SimpleData<Student>(student)
handleMyData(data)
val studentData = data.get()
}
fun handleMyData(data: SimpleData<Person>) {
val personData = data.get()
}
}
open class Person {
}
class Student(val name: String, val age: Int) : Person() {
}
在SimpleData的泛型T前面加上了out关键字,这意味着T只能出现在out位置上,不能出现在in位置上,也意味着SimpleData在泛型T上是协变的
由于泛型T不能出现在in位置上,所以不能使用set()方法为data参数赋值;但如果确实需要在in位置上出现,那么可以使用val关键字修饰,或者用private来修饰,来表明该泛型T对于外部而言是只读的,不可修改的
在Kotlin中,Kotlin已经默认给许多内置的API加上了协变的声明,其中就包括了各种集合的类与接口
例如:在Kotlin中的List本身就是只读的,因此它天然支持协变;如果需要给List添加数据,那么需要使用MutableList才行
泛型的逆变
泛型的逆变与协变完全相反,假设定义一个MyClass的泛型类,其中A是B的子类型,同时MyClass<B>是MyClass<A>的子类型,那么可以称之为MyClass在T这个泛型上是逆变的
interface Transformer<in T> {
fun transform(t: T): String
}
class SimpleData2 {
fun main() {
val trans = object : Transformer<Person> {
override fun transform(t: Person): String {
return "name = ${t.name}, age = ${t.age}"
}
}
handleTransformer(trans)
}
fun handleTransformer(trans: Transformer<Student>) {
val student = Student("Kotlin", 19)
val result = trans.transform(student)
}
}
open class Person {
var name: String? = ""
var age: Int? = 0
}
class Student(name: String, age: Int) : Person() {
}
Kotlin在提供协变和逆变的功能时,就已经把所有潜在的类型转换安全隐患全部考虑进去了,只要严格按照语法规则,让泛型在协变时只出现在out位置上,逆变时只出现在in位置上,就不会存在类型转换异常的情况了
注意:虽然@UnsafeVariance注解可以打破语法规则,但可能存在额外的风险,因此需要谨慎使用