10.6 泛型的高级特性
10.6.1 对泛型进行实化
泛型实化是Kotlin中新引入的概念。
首先我们需要了解一点,所有基于JVM的语言,如Java、Kotlin,它们的泛型功能都是通过类型擦除来实现的。
这种机制使得我们不能使用a is T
或者T::class.java
这样的语法,因为T
的实际类型在运行时已经被擦除了。
然而不同的是,Kotlin提供了内联函数的概念,内联函数中的代码在编译的时候会自动被替换到调用它的地方,这样的话就不存在泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛型声明。
这也是为什么只有内联函数才能在泛型声明的地方加上reified
关键字来进行实化。
示例代码如下:
inline fun <reified T> getGenericType() = T::class.java
这里我们对T
泛型进行了实化,这样我们就能在getGenericType()
这个方法里直接调用T::class.java
等方式获取该泛型的实际类型。
我们写个测试方法来对这个内联函数进行调用测试。
@Test
fun test01() {
val result1 = getGenericType<String>()
val result2 = getGenericType<Int>()
println("result1 is $result1")
println("result2 is $result2")
}
查看输出。
result1 is class java.lang.String
result2 is class java.lang.Integer
这样一来,我们就对泛型实化的概念有一定了解了。
10.6.2 泛型实化的应用
本节举例了一个泛型实化的应用,来加深印象。
首先定义了一个内联函数startActivity()
:
inline fun <reified T> startActivity(context: Context) {
val intent = Intent(context, T::class.java)
context.startActivity(intent)
}
这个函数的泛型加上了reified
的关键字,说明该泛型已经被实化了。
那么在方法中我们直接在创建Intent
对象的时候,第二个参数直接获取T
泛型对应的类型即可。
在调用的时候直接这么写:
@Test
fun test02() {
startActivity<TestActivity>(context = context)
}
看,是不是方便了很多。
不过我们通常在页面跳转的时候需要给Intent
赋值一些参数,这时候我们就可以借助之前几章高阶函数的知识,来对代码进行优化。
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
val intent = Intent(context, T::class.java)
intent.block()
context.startActivity(intent)
}
如此一来,我们就可以很轻松的给intent
进行赋值参数了。
@Test
fun test02() {
startActivity<TestActivity>(context){
putExtra("param1", "data")
putExtra("param2", 123)
}
}
通过这个应用,相信大家已经能领会到泛型实化的应用场景了。
10.6.3 泛型的协变
协变和逆变内容很多同学都比较薄弱,这源于对java中的泛型就缺乏深入的掌握。
因此笔者建议先看一下凯哥的视频——Kotlin 的泛型,再继续看本文章这两节的内容。
在学习协变和逆变之前,先了解一个约定。
一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以称它为in
位置,而它返回值是输出数据的地方,可以称它为out
位置。
如图所示:
有了这个前提,我们来定义三个类:
open class Person(val name: String, val age: Int)
public class Student(name: String, age: Int): Person(name, age)
public class Teacher(name: String, age: Int): Person(name, age)
我们知道子类对象是可以赋值给父类的引用,这是完全没问题的。
但是思考一个问题:那么子类类型的List
能赋值给父类类型的List
吗?
也就是说List<Person> = List<Student>
,可以这么定义吗?
这么定义是不合法的,存在安全隐患,举例说明:
class SimpleData<T> {
private var data: T? = null
fun set(t: T?) {
data = t
}
fun get(): T? {
return data
}
}
上面定义了一个泛型类SimpleData
。
再定义一个handleSimpleData
函数,他的参数是SimpleData<Person>
,如下。
@Test
fun test03() {
val student = Student("Tom", 19)
var data = SimpleData<Student>()
data.set(student)
handleSimpleData(data = data) // 实际上这里会报错,我们假设它能编译通过
val get = data.get()
}
fun handleSimpleData(data: SimpleData<Person>) {
val teacher = Teacher("Jack", 19)
data.set(teacher)
}
我们假设调用handleSimpleData()
方法的时候可以传入SimpleData<Student>
,也就是handleSimpleData(data = data)
这行代码能正常执行。
这时候如果在handleSimpleData()
方法中定义一个Teacher
对象,然后调用data.set(teacher)
把这个Teacher
对象赋值给SimpleData
中的data
属性上。
这时候就会出现类型转换错误,因为handleSimpleData
的形参实际上是一个SimpleData<Student>()
,但是data.set(teacher)
的却是一个Teacher
类的对象。
泛型协变:假如定义了一个
MyClass<T>
的泛型类,其中A
是B
的子类型,同时MyClass<A>
又是MyClass<B>
的子类型,那么我们就可以称MyClass
在T
这个泛型上是协变的。
如何让MyClass<A>
成为MyClass<B>
的子类呢,就是在泛型T
的声明前加上out
关键字(相当于Java中的? extend
关键字)。
不过这样也加了一个限制,T
只能出现在out
位置,也就是其在泛型类型数据上是只读的。
class SimpleData<out T>(val data: T?) {
fun get(): T? {
return data
}
}
由于SimpleData
类已经进行了协变声明,那么SimpleData<Student>
自然就是SimpleData<Person>
的子类,所以就可以安全地向handleMyData()
方法中传递参数了。
@Test
fun test03() {
val student = Student("Tom", 19)
var data = SimpleData<Student>(student)
handleSimpleData(data = data)
val get = data.get()
}
fun handleSimpleData(data: SimpleData<Person>) {
val get = data.get()
}
10.6.4 泛型的逆变
泛型的逆变与协变刚好相反,我们用一张图来看:
直接看示例,先定义一个Transformer
接口,用来做一些转换操作
interface Transformer<T> {
fun transformer(t: T): String
}
定义一个方法handleTransformer
,接收一个子类的List
的参数,然后在测试方法中传入父类的List
。
@Test
fun test04() {
val trans = object : Transformer<Person> {
override fun transformer(t: Person): String {
return "${t.name} ${t.age}"
}
}
handleTransformer(trans = trans) // 这行会报错
}
fun handleTransformer(trans: Transformer<Student>) {
var student = Student("Tom", 19)
var result = trans.transformer(student)
}
这段代码从安全的角度上分析是没有问题的,但是却是不允许的。
如果想让父类的List
赋值给子类List
的引用,就需要逆变声明。如下:
interface Transformer<in T> {
fun transformer(t: T): String
}
这样,就能正常编译运行了。
同样,加上了逆变声明后,只能定义在in
的位置上,而且可写不可读。
值得一提的是,可以用注解@UnsafeVariance
摆脱位置上的限制,但是一旦使用了这个注解,也就告诉了编译器不做这块的安全转换检查,如有异常,会直接报错。
下面是一个安全转换错误的示例:
interface Transformer<in T> {
fun transformer(name: String, age: Int): @UnsafeVariance T
}
@Test
fun test04() {
val trans = object : Transformer<Person> {
override fun transformer(name: String, age: Int): Person {
return Teacher(name, age)
}
}
handleTransformer(trans = trans)
}
fun handleTransformer(trans: Transformer<Student>) {
val result = trans.transformer("Tom", 19)
}
最后运行代码的时候,查看会看到控制台中类型转换错误的日志输出。
书中关于泛型的高级特性就到此为止了。祝好!
附上文中代码地址
《第一行代码》Kotlin讲堂知识整理