[译]Kotlin的Reified实化类型参数

让我们来想一想在Kotlin中你可以用类名来做些什么,想想你在源码中编写类名的所有场景。我想到了以下15种情景。

  • 1、定义一个成员属性
1
private val thing: Thing
  • 2、函数参数
1
fun doSomething(thing: Thing) {}
  • 3、类型实参
1
val list = listOf<Thing>()
  • 4、类型形参约束
1
class Item<T : Thing>
  • 5、强制类型转换
1
something as Thing
  • 6、定义一个类
1
open class Thing {}
  • 7、继承一个类
1
class Other : Thing()
  • 8、导入一个类
1
import com.example.Thing
  • 9、给类名定义typealias别名
1
typealias Thingy = Thing
  • 10、构建一个对象
1
val thing = Thing()
  • 11、捕获一个错误类型
1
catch (e: ExceptionalThing)
  • 12、调用静态方法
1
Thing.doStaticStuff()
  • 13、定义函数引用
1
val function = Thing::doSomething
  • 14、类型比较
1
something is Thing
  • 15、获得类的一个Class对象:
1
val clazz = Thing::class.java

问题来了

现在,最大的问题是:

在上面哪些案例中我们应该使用泛型类型参数引用而不是真实的类名?

换句话说,上面15种案例中哪些我们可以用类型参数比如 T去替代Thing类呢?

你在哪些地方可以引用类型参数?

你想到了什么? 这是我能想到的 - 上面的案例1-5都可以采用类型参数。让我们一起展示所有五个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class GenericThing<T>(constructorArg: T) {
// 1. Define a member property 定义一个成员属性
private val thing: T = constructorArg
// 2. Define the type of a function argument 定义函数参数
fun doSomething(thing: T) = println(thing)
// 3. Use as a type argument 定义泛型类型实参
fun emptyList() = listOf<T>()
// 4. Use as a type parameter constraint, and... 使用作为类型形参约束
// 5. Cast to the type (produces "unchecked cast" warning) 强制类型转换
fun <U : T> castIt(): T = thing as U
}

你在哪些地方可以不引用类型参数?

引用类型参数适用于案例1-5。但是对于案例6-15,如果我们要在(例如,Thing或ExceptionalThing)的地方替换类型参数(例如T),则最终会出现编译器错误。

  • 在某些情况下,使用类型参数是没有意义的。例如,在案例6中我们正在定义一个类 - 根据其类型参数之一定义一个类有什么意义呢?
  • 在其他情况下,Java和Kotlin通过反射提供一些变通的方法,例如构建对象(案例10) 但在某些情况下,能够使用类型参数肯定会很好。特别是,案例14和15–比较类型和分配类对象 - 在某些情况下会非常方便。

Reified类型实化参数介绍

Java限制了哪些类型是reifiable - reifiable也就意味着它们“在运行时完全可用”(具体可查阅:Java SE specs on reifiable types),泛型类型参数通常在编译期间被擦除,但是在Kotlin中的reified类型参数的情况下, 由于底层语法下一些巧妙的技巧,让运行时也能准确拿到泛型参数类型信息。

Reified类型参数仅适用于函数(或具有get()函数的扩展属性),并且仅适用于声明为inline内联的函数。这是一个例子:

1
inline fun <reified T> Any.isInstanceOf(): Boolean = this is T

当您将函数标记为inline时,编译器会把实现内联函数的字节码插入到每次调用发生的地方。这就是reified类型的工作原理 - 具体实际类型在调用地方是已经知道的,因此在调用x.isInstanceOf<String>()有效地把x编译为String.

reified类型实化参数经常被使用到的几个地方

上面的案例15是许多Kotlin开发人员最喜欢的案例。假设我们有一个User类,以及我们想要读取的JSON字符串

1
2
3
4
5
6
data class User(val first: String, val last: String)
val json = """{
"first": "Sherlock",
"last": "Holmes"
}""

在Java序列化库(如Gson)中,当您想要反序列化该JSON字符串时,您最终必须将Class对象作为参数传递,以便Gson知道您想要的类型。

1
User user = new Gson().fromJson(getJson(), User.class);

现在,让我们一起展示reified类型实化参数的魔法 我们将创建一个非常轻量级的扩展函数来包装Gson方法:

1
2
inline fun <reified T> Gson.fromJson(json: String) =
fromJson(json, T::class.java)

现在,在我们的Kotlin代码中,我们可以反序列化JSON字符串,甚至根本不需要传递类型信息!

1
val user: User = Gson().fromJson(json)

Kotlin根据它的用法推断出类型 - 因为我们将它分配给User类型的变量,Kotlin使用它作为fromJson()的类型参数。或者,您可以使类型推断其他的类:

1
val user = Gson().fromJson<User>(json)

在这种情况下,从传递给fromJson()的类型参数推断出user类型。

参考