我试图了解reified
关键字的用途,显然是it's allowing us to do reflection on generics。
然而,当我把它放在外面时,效果一样好。任何人都在关心何时会产生实际的差异?
答案 0 :(得分:212)
reified
对fun <T> myGenericFun(c: Class<T>)
在myGenericFun
等通用函数的正文中,您无法访问T
类型,因为它的仅在编译时可用但erased在运行时。因此,如果要将泛型类型用作函数体中的普通类,则需要显式将类作为参数传递,如myGenericFun
中所示。
如果使用具体化 inline
创建T
函数,即使在运行时也可以访问T
的类型,因此您不需要另外传递Class<T>
。您可以使用T
,就像它是正常的类一样,例如您可能想要检查变量是否是 T
的实例,您可以轻松地执行此操作:myVar is T
。
这种inline
reified
类型T
函数如下所示:
inline fun <reified T> myGenericFun()
reified
如何运作您只能将reified
与inline
function结合使用。这样的函数使得编译器将函数的字节码复制到使用函数的每个地方(该函数正在&#34;内联&#34;)。
当您使用reified类型调用内联函数时,编译器知道用作类型参数的实际类型,并修改生成的字节码以直接使用相应的类。
因此,myVar is T
之类的调用在字节码和运行时变为myVar is String
(如果类型参数为String
)。
让我们看一个显示reified
有用的示例。
我们想要为名为String
的{{1}}创建一个扩展函数,该函数尝试将JSON字符串转换为具有由函数的泛型toKotlinObject
指定的类型的普通Kotlin对象。我们可以使用com.fasterxml.jackson.module.kotlin
,第一种方法如下:
a)没有统一类型的第一种方法
T
fun <T> String.toKotlinObject(): T {
val mapper = jacksonObjectMapper()
//does not compile!
return mapper.readValue(this, T::class.java)
}
方法采用一种它应该解析readValue
的类型。如果我们尝试获取类型参数JsonObject
的{{1}},编译器会抱怨:&#34;无法使用&#39; T&#39;作为具体的类型参数。请改用班级。&#34;
b)使用明确的Class
参数
T
作为一种变通方法,可以将Class
fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
val mapper = jacksonObjectMapper()
return mapper.readValue(this, c.java)
}
作为方法参数,然后将其用作Class
的参数。这是有效的,并且是通用Java代码中的常见模式。它可以如下调用:
T
c)Kotlin方式:readValue
使用data class MyJsonType(val name: String)
val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)
函数与reified
类型参数inline
,可以以不同方式实现此功能:
reified
除此之外,T
无法inline fun <reified T: Any> String.toKotlinObject(): T {
val mapper = jacksonObjectMapper()
return mapper.readValue(this, T::class.java)
}
,Class
可以像使用普通类一样使用T
。对于客户端,代码如下所示:
T
json.toKotlinObject<MyJsonType>()
类型的内联函数无法从Java 代码中调用。
答案 1 :(得分:7)
reified
类型泛型
在 Kotlin 中使用泛型时,我们可以对任何类型 T
的值执行操作:
fun <T> doSomething(value: T) {
println("Doing something with value: $value") // OK
}
这里我们隐式调用了 toString()
的 value
函数,并且可以正常工作。
但是我们不能直接对 T
类型执行任何操作:
fun <T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // Error
}
让我们了解这个错误的原因。
类型擦除
在上面的代码中,编译器给出了一个错误:Cannot use 'T' as reified type parameter. Use a class instead.
这是因为在编译时,编译器从函数调用中删除了类型参数。
例如,如果您将函数调用为:
doSomething<String>("Some String")
编译器移除了类型参数部分 <String>
,运行时剩下的就是:
doSomething("Some String")
这称为类型擦除。因此,在运行时(在函数定义内部),我们不可能确切知道 T
代表哪种类型。
Java 解决方案
Java 中这种类型擦除问题的解决方案是传递一个额外的参数,用 Class
(在 Java 中)或 KClass
(在 Kotlin 中)指定类型:
fun <T: Any> doSomething(value: T, type: KClass<T>) {
println("Doing something with type: ${type.simpleName}") // OK
}
这样我们的代码就不会受到类型擦除的影响。但是这个解决方案很冗长而且不是很优雅,因为我们必须声明它并用一个额外的参数调用它。此外,必须指定类型绑定 Any
。
类型具体化
上述问题的最佳解决方案是 Kotlin 中的类型具体化。类型参数前的 reified
修饰符可以在运行时保留类型信息:
inline fun <reified T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // OK
}
在上面的代码中,多亏了 reified
类型参数,我们在对 T
类型执行操作时不再出现错误。让我们看看 inline
函数如何使这种魔法成为可能。
inline
函数
当我们将函数标记为 inline
时,编译器会在调用该函数的任何地方复制该 inline
函数的实际主体。由于我们将 doSomething()
函数标记为 inline
,因此以下代码:
fun main() {
doSomething<String>("Some String")
}
被编译为:
fun main() {
println("Doing something with type: ${String::class.simpleName}")
}
因此,上面显示的两个代码片段是等效的。
在复制 inline
函数的主体时,编译器还将类型参数 T
替换为在函数调用中指定或推断的实际类型参数。例如,注意如何将类型参数 T
替换为实际类型参数 String
。
reified
类型的类型检查和类型转换reified
类型参数的主要目标是了解类型参数 T
在运行时表示的确切类型。
假设我们有一个不同类型水果的列表:
val fruits = listOf(Apple(), Orange(), Banana(), Orange())
我们希望在一个单独的列表中过滤所有 Orange
类型,如下所示:
val oranges = listOf(Orange(), Orange())
没有reified
为了过滤水果类型,我们可以在 List<Any>
上编写一个扩展函数,如下所示:
fun <T> List<Any>.filterFruit(): List<T> {
return this.filter { it is T }.map { it as T } // Error and Warning
}
在此代码中,首先我们过滤类型,并且仅在元素的类型与给定的类型参数匹配时才采用元素。然后我们将每个元素转换为给定的类型参数和 return
List
。但是有两个问题。
类型检查
在类型检查 it is T
时,编译器引入了另一个错误:Cannot check for instance of erased type: T
。这是您可能因类型擦除而遇到的另一种错误。
类型转换
在类型转换 it as T
时,我们还会收到警告:Unchecked cast: Any to T
。由于类型擦除,编译器无法确认类型。
reified
类型的救援
我们可以通过将函数标记为 inline
并将类型参数设为 reified
来轻松解决这两个问题:
inline fun <reified T> List<Any>.filterFruit(): List<T> {
return this.filter { it is T }.map { it as T }
}
然后像下面这样调用它:
val oranges = fruits.filterFruit<Orange>()
为了更容易演示,我展示了这个函数。为了过滤集合中的类型,已经有一个标准库函数filterIsInstance()
。此函数以类似的方式使用了 inline
和 reified
修饰符。您可以简单地将其调用如下:
val oranges = fruits.filterIsInstance<Orange>()
reified
参数作为参数传递reified
修饰符使函数可以将类型参数作为类型参数传递给另一个具有 reified
修饰符的函数:
inline fun <reified T> doSomething() {
// Passing T as an argument to another function
doSomethingElse<T>()
}
inline fun <reified T> doSomethingElse() { }
reified
类型的泛型有时类型参数可以是泛型类型。例如,函数调用List<String>
中的doSomething<List<String>>()
。由于具体化,可以知道整个类型:
inline fun <reified T> getGenericType() {
val type: KType = typeOf<T>()
println(type)
}
这里的 typeOf()
是一个标准库函数。如果您将该函数调用为 println()
,则上面的 kotlin.collections.List<kotlin.String>
函数将打印 getGenericType<List<String>>()
。 KType
包括 KClass
、类型参数信息和可空性信息。知道 KType
后,您可以对其进行反思。
未声明 inline
类型参数的 reified
函数可以作为常规 Java 函数从 Java 中调用。但是用 reified
类型参数声明的那些不能从 Java 调用。
即使您使用反射调用它,如下所示:
Method method = YourFilenameKt.class.getDeclaredMethod("doSomething", Object.class);
method.invoke("hello", Object.class);
你得到了 UnsupportedOperationException: This function has a reified type parameter and thus can only be inlined at compilation time, not called directly.
在很多情况下,reified
类型可以帮助我们摆脱以下错误和警告:
Error: Cannot use 'T' as reified type parameter. Use a class instead.
Error: Cannot check for instance of erased type: T
Warning: Unchecked cast: SomeType to T
就是这样!希望有助于理解 reified
类型的本质。
答案 2 :(得分:4)
简单
* reified是在编译时授予使用权限(在函数内访问T)
例如:
val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
println(userObject.name)
使用方式:
{{1}}