opoojkk

Kotlin泛型和reified

xx

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

Type Erasure (The Java™ Tutorials > Learning the Java Language > Generics (Updated))

由于泛型在编译后会被擦除,如果没有定义边界,就会被替换为 Object,因此在运行时无法获取具体的类型信息。Kotlin 的泛型实现同样遵循这一规则。

当我们尝试写一个扩展函数来启动 Activity 时,就会遇到这个问题:

1
2
3
4
5
inline fun <T : Activity> Activity.startActivity(block: ((Intent) -> Unit) = {}) {
    val intent = Intent(this, T::class.java) // IDE 会报错
    block.invoke(intent)
    startActivity(intent)
}

这里会报错,是因为在运行时无法确定 T 的具体类型,也就不知道要启动哪个 Activity。

Kotlin 提供了 reified 关键字来解决这个问题,它允许在内联函数中保留泛型的实际类型,从而在运行时使用:

1
2
3
4
5
inline fun <reified T : Activity> Activity.startActivity(block: ((Intent) -> Unit) = {}) {
    val intent = Intent(this, T::class.java)
    block.invoke(intent)
    startActivity(intent)
}

调用方式如下:

1
2
3
fun Activity.startMainActivity() {
    startActivity<MainActivity>()
}

反编译成 Java 代码后,大致如下:

1
2
3
4
5
public static final void startMainActivity(@NotNull Activity $this$startMainActivity) {
    Intrinsics.checkNotNullParameter($this$startMainActivity, "<this>");
    Intent intent$iv = new Intent((Context)$this$startMainActivity, MainActivity.class);
    $this$startMainActivity.startActivity(intent$iv);
}

可以看到,reified 的作用是把泛型类型直接写进了代码中,从而在运行时可以获取具体的 Class 对象。


需要注意的是,如果我们只是使用泛型实例的方法,并不需要具体类型信息,就不会有问题:

1
2
3
fun <T : Activity> finish(t: T) {
    t.finish()
}

这里编译器能够正常处理,因为我们并不是在运行时获取类型,而是调用泛型实例的成员方法。Java 的继承机制保证了 Activity 的子类都可以访问 finish() 方法,所以实例方法不会受类型擦除的影响。

Java中的泛型在编译后是会被擦除的,如果没有定义边界,会被用Object取代,因此没有办法在运行时获取类的信息,Kotlin同理。

当定义一个扩展方法启动activity时,也就拿不到真实的类型。

1
2
3
4
5
inline fun <T : Activity> Activity.startActivity(block: ((Intent) -> Unit) = {}) {
    val intent = Intent(this, T::class.java)
    block.invoke(intent)
    startActivity(intent)
}

当这样写时,IDE会提示错误,因为不知道T具体的类型,也就不知道启动的是哪个Activity。

Kotlin中新增了关键字reified解决这种状况。

1
2
3
4
5
inline fun <reified T : Activity> Activity.startActivity(block: ((Intent) -> Unit) = {}) {
    val intent = Intent(this, T::class.java)
    block.invoke(intent)
    startActivity(intent)
}

实际上,这种写法是将传入的类,写在了代码中。来调用该方法

1
2
3
fun Activity.startMainActivity() {
    startActivity<MainActivity>()
}

借助反编译看对应的Java代码

1
2
3
4
5
6
7
   public static final void startMainActivity(@NotNull Activity $this$startMainActivity) {
      Intrinsics.checkNotNullParameter($this$startMainActivity, "<this>");
      int $i$f$startActivity = 0;
      Intent intent$iv = new Intent((Context)$this$startMainActivity, MainActivity.class);
      int var5 = 0;
      $this$startMainActivity.startActivity(intent$iv);
   }

当需要一个泛型的实例时,有些差别。

1
2
3
fun <T : Activity> finish(t: T) {
    t.finish()
}

上面的代码是可以正常编译执行的。和上面启动Activity的是不同的。上面的方法需要的是一个类型,如果不知道具体的类型就不知道要启动哪个Activity。这里执行的是泛型实例的一个方法,泛型只是把类型用边界类代替了,finish()方法Activity还是会提供的,Java继承的特性导致Activity的字类都可以访问到,也就是说实例方法是不受影响的。

标签:
Categories: