学习Kotlin之泛型实化
对泛型进行实化
Java中是没有泛型实化这个概念的,不过为了深刻理解泛型实化,还需要了解一下Java的泛型擦除机制。
在JDK 1.5之前,Java是没有泛型功能的,那个时候诸如List之类的数据结构可以存储任意类型的数据,取出数据时也要手动向下转型,不仅麻烦,还很危险。于是在JDK 1.5中,Java引入了泛型功能。
实际上,Java的泛型功能是通过类型擦除机制实现的。就是说泛型对于类型的约束只在编译时期存在,运行的时候仍然会按照JDK 1.5之前的机制来运行,JVM是识别不出来我们在代码中指定的泛型类型的。比如:我们创建了一个List<String>集合,在编译时期只能向集合中添加字符串类型的元素,但是在运行时期JVM并不能知道它本来只打算包含哪些类型的元素,只能识别出来它是个List。
所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,其中也包括了Kotlin。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际类型在运行的时候已经被擦除了。
然而不同的是,Kotlin提供了内联函数的概念,内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话就不存在泛型类型擦除的问题了。因为代码在编译之后会直接使用实际的类型替代内联函数中的泛型声明,其工作原理图如下:
bar()是一个带有泛型类型的内联函数,foo()函数调用了bar()函数,在代码编译之后,bar()函数中的代码将可以获得泛型的实际类型。
这说明,Kotlin中是可以将内联函数中的泛型进行实化的。
那么具体怎么写呢?
首先,该函数必须是内联函数。其次,在声明泛型的地方加上reified关键字来表示该泛型要进行实化。
inline fun <reified T> getGenericType() {
}
上述的泛型T就是一个被实化的泛型。
那么借助泛型实化,可以实现什么样的效果呢?
这里我们准备实现一个获取泛型实际类型的功能:
inline fun <reified T> getGenericType() = T::class.java
虽然只有一行代码,但是却实现了一个Java中不可能实现的功能:getGenericType()函数直接返回了当前指定泛型的实际类型。
T.class这样的语法在Java中是不合法的,但是在Kotlin中,借助泛型实化功能就可以使用T::class.java这样的语法。
现在我们对getGenericType()函数进行测试:
fun main() { val result1 = getGenericType<String>() val result2 = getGenericType<Int>() println("result1 is $result1") println("result2 is $result2") }
这里给getGenericType()函数指定了两种不同的泛型,由于getGenericType()函数会将指定泛型的具体类型返回,因此这里直接将返回结果打印。
泛型实化的作用
泛型实化功能允许我们在泛型函数中获得泛型的实际类型,这就使得类似于a is T、T::class.java这样的语法成为了可能。而灵活运用这一特性将可以实现一些不可思议的语法结构。
比如使用Intent的时候:
val intent = Intent(context,SecondActivity::class.java) context.startActivity(intent)
会有SecondActivity::class.java这样的语法结构,而Kotlin的泛型实化功能能让我们有用更好的选择。
新建一个reified.kt文件,编写如下代码:
inline fun <reified T> startActivity(context: Context) { val intent = Intent(context,T::class.java) context.startActivity(intent) }
这里定义了一个startActivity()函数,该函数接收一个Context参数,并同时使用inline和reified关键字让泛型T成为了一个被实化的泛型。接下来就是神奇的地方了,Intent接收的第二个参数本来应该是一个具体Activity的Class类型,但由于现在T已经是一个被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用Context的startActivity()方法来完成Activity的启动。
现在想要启动SecondActivity,只需要这样写;
startActivity<SecondActivity>(context)
Kotlin能够识别出指定泛型的实际类型,并启动相应的Activity。
代码精简了许多,这就是泛型实化带来的功能。
不过现在的startActivity()函数还是有问题的,因为在启用Activity的时候可能会使用Intent附带一些参数。这里需要借助高阶函数来解决,添加一个新的startActivity()函数重载,如下所示:
inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) { val intent = Intent(context,T::class.java) intent.block() context.startActivity(intent) }
这里新增了一个函数类型参数,并且它的函数类型是定义在Intent类中的。
创建了Intent实例后,调用该函数类型参数,并把Intent实例传入,这样调用startActivity()函数的时候就可以在Lambda表达式中为Intent传递参数了,如下所示:
startActivity<SecondActivity>(this) { putExtra("param1", "data") putExtra("param2", 123) }
这样我们就做到了简化启动Activity的用法了。