Kotlin 泛型擦除与实化类型

泛型擦除

由于JVM虚拟机中没有泛型,因此泛型类的类型在编译时都会被擦除,所谓的擦除是指当定义一个泛型时,例如List<String>类型,运行时它只是List,并不体现String类型。这一点Kotlin与Java是一样的,泛型在运行时都会被擦除。

接下来我们通过一个案例来解释泛型在程序运行时会被擦除,具体代码如下所示。

package com.coolcou.chapter07
fun main(args: Array<String>) {
    // 定义一个类型为List<String>的集合
    val list1 = listOf("a", "b", "c")
    // 定义一个类型为List<Int>的集合
    val list2 = listOf(1, 2, 3)
    // 打印集合的类型
    println(list1.javaClass)
    println(list2.javaClass)
   // 判断这两个集合数据类型是否一致
   println(list1.javaClass == list2.javaClass)
}

运行结果:

class java.util.Arrays$ArrayList
class java.util.Arrays$ArrayList
true

根据运行结果可知,List<String>List<Int>在程序运行期间类型是一样的,因此说明泛型在运行时都会被擦除。

泛型通配符

在Java程序中,如果不知道泛型的具体类型时,可以用“?”通配符来代替具体的类型,而在Kotlin中则使用“*”来代替泛型的具体类型,这个“*”就被称为泛型通配符,它只能在“<>”中使用。

接下来我们通过一个案例演示如何使用泛型通配符“*”,具体代码如下所示。

package com.coolcou.chapter07
open class Food(val name: String)
open class Flower(val name: String)
class Rice : Food(" 大米")
class Rose : Flower(" 玫瑰")
class Container<T>(var content: T) // 定义一个泛型类Container
fun printInfo(container: Container<*>) {
    val content = container.content
    if (content is Food) {
       println(content.name)
   } else if (content is Flower) {
       println(content.name)
   }
}
fun main(args: Array<String>) {
   val riceContainer = Container<Rice>(Rice())
   val roseContainer = Container<Rose>(Rose())
   printInfo(rice)
   printInfo(rose)
}

运行结果:

大米
玫瑰

上述代码中,通过printInfo()方法来打印Container泛型类中传递的食物或者鲜花,printInfo()方法可以接收Container<out Food>也可以接收Container<out Rose>,由于不能明确需要传入的是什么类型,因此使用“*”代替。

在main()函数中,分别创建了两个泛型类Container的实例对象—riceContainer和roseContainer,其中riceContainer传递的参数类型为Rice,roseContainer传递的参数类型为Rose,将这两个实例对象传递到printInfo()方法中即可打印运行结果。

星投影

当对泛型的实参一无所知,但仍然希望用安全的方式使用它时,此时有一种安全的方式—星投影,星投影就是将泛型中的“*”等价于泛型中的注解out与in对应的协变类型参数与逆变类型参数,泛型的每个具体实例化将是该投影的子类型,Kotlin为此提供了星投影语法,我们以自定义的泛型类A<T>为例来演示星投影语法,具体如下。
(1)对于泛型类A<out T>,其中T是一个具有上界TUpper的协变类型参数,A<*>等价于A<out TUpper>,这意味着当T未知时,可以安全地从A<*>中读取TUpper的值。
(2)对于泛型类A<in T>,其中T是一个逆变类型参数,A<*>等价于A<in Nothing>,由于Nothing类型表示没有任何值,因此这意味着当T未知时,没有安全的方式写入A<*>
(3)对于泛型类A<T>,其中T是一个具有上界TUpper的不型变类型参数,A<*>在读取值时等价于A<out TUpper>,而在写值时等价于A<in Nothing>

如果泛型类型具有多个类型参数,则每个类型参数都可以进行单独的星投影,例如,如果声明一个泛型类B<in T,out U>,则此时可以根据星投影语法推测出以下星投影。

  • 如果泛型类为B<*, String>,则该泛型类等价于B<in Nothing, String>
  • 如果泛型类为B<Int,*>,则该泛型类等价于B<Int, out Any?>
  • 如果泛型类为B<*,*>,则该泛型类等价于B<in Nothing, out Any?>

实化类型

泛型擦除中已经讲过泛型在运行时会被擦除,这样就无法知道某一个泛型形参在使用时具体是什么类型的泛型实参,在Java中,可以通过反射获取泛型的真实类型,而在Kotlin中,要想获取泛型的实参类型,则需要在内联函数(inline关键字定义的函数)中使用reified关键字修饰泛型参数才可以,这样的参数称为实化类型。reified关键词必须要和inline一起使用,因为只有内联的泛型函数才可以在运行时获取泛型实参的类型。

接下来我们通过一个在Any类中添加一个拓展方法isType()的案例来判断泛型的实参类型,具体代码如下所示。

package com.coolcou.chapter07
inline fun <reified T> Any.isType(): Boolean {
    if (this is T) {
        return true
    }
    return false
}
fun main(args: Array<String>) {
    println("abc".isType<String>())
   println(123.isType<String>())
}

运行结果:

true
false

根据运行结果可知,如果想把泛型参数类型变为实化类型,则这个泛型参数所在的函数必须是inline函数,而且泛型参数前必须用reified关键字来修饰。

酷客教程相关文章:

赞(0)

评论 抢沙发

评论前必须登录!