kotlin的泛型规则原理上和java是一样的,不过kotlin在java的基础上对泛型扩展了一些新功能,其中就包括对类型擦除规则的处理。
类型擦除:
泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,这个过程被称为类型擦除
意思就是说泛型类型T的真实类型编译器在编译阶段是知道的,一旦进入java虚拟机运行这个T的类型信息会被擦除,以集合类为例:
val l1 = arrayListOf() val l2 = arrayListOf () Log.e("tag", "${l1.javaClass}--${l2.javaClass}") // E/tag: class java.util.ArrayList--class java.util.ArrayList
我们声明了两个类型分别是String和Int的ArrayList对象,但是打印出来的对象类型仅仅只有ArrayList,这个时候java虚拟机并不知道你的泛型类型,因为类型信息被擦除了。java之所以以类型擦除的形式去实现泛型是因为java5之后才引入的泛型,java为了兼容5之前的版本编译器会先把泛型T替换为最基本的Object类型(这个操作就是擦除类型),然后再做对应的类型转换并将类型信息提供给编译器,但是java虚拟机拿到的仍然是Object类型的对象,这就是为什么打印出来的类型信息里面没有String和Int。
网上的一段话:
类型擦除可以做到编译时类型安全,又可以通过保持字节码同之前的 Java 版本相同来实现向后兼容。
但是如果我们需要在泛型方法中使用这个泛型T的话就会因为类型被擦出而导致编译器报错,因为jvm拿不到这个类型:
我们声明了一个Activity的扩展方法,执行逻辑就是往T这个activity进行跳转,这个时候编译器就会如上所说出现报错。kotlin为了解决这个问题为我们提供了reified关键字。
二、reified说reified关键字之前先简单的说一下内联函数。被inline关键字修饰的函数就是内联函数,他的原理是:在编译时期,把调用这个函数的地方用这个函数的方法体进行替换。我们一般会把代码中的公共逻辑抽取出来作为一个公共的函数提供给各个类使用,这样就可以大大降低代码量,而内联函数则是倒过来的,表面上看他仍然是个公共函数但是在编译时期调用这个函数的地方会被函数方法体替换:
private inline fun demo(a: (n: Int) -> Int) {
Log.e("tag", "${a(1)}")
}
调用:
demo {
return@demo it + 10
}
//等于在这里又写了一遍Log.e("tag", "${1 + 10}")
网上的一段话:
也就是说inline关键字实际上增加了代码量,但是提升了性能,而且增加的代码量是在编译期执行的,对程序可读性不会造成影响
基于内联函数的这个特性他就可以和reified关键字搭配使用来解决上述类型擦除引入的问题:
inline funActivity.to() { startActivity(Intent(this, T::class.java)) } class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) this.to () //等同于startActivity(Intent(this, MainActivity2::class.java)) } }
可以看到并不会报错,在编译时期startActivity(Intent(this, T::class.java))中的T实际就是MainActivity2,他的类型并没有被擦除,kotlin用这种方式解决了泛型方法里面泛型T因为类型擦除而无法被使用的问题。实际上就是通过内联写了一个公共方法并且这个方法是泛型公共方法,类型擦除在这里已经被架空了。这个过程就是泛型的实化
kotlin的泛型后续文章会继续介绍,包括协变、逆变这些和java相通却又不同的东西


