hl3*_*kel 98 generics kotlin kotlin-reified-type-parameters
我试图了解reified
关键字的目的,显然它允许我们对泛型进行反思.
但是,当我把它放在外面时它的效果一样好.任何人都在关心何时产生实际差异?
s1m*_*nw1 271
reified
好处fun <T> myGenericFun(c: Class<T>)
Run Code Online (Sandbox Code Playgroud)
在泛型函数的主体中myGenericFun
,您无法访问该类型,T
因为它仅在编译时可用,但在运行时被擦除.因此,如果要将泛型类型用作函数体中的普通类,则需要将类显式作为参数传递,如下所示myGenericFun
.
如果inline
使用reified 创建函数T
,则T
即使在运行时也可以访问类型,因此您无需Class<T>
另外传递.您可以T
像使用普通类一样工作,例如,您可能想要检查变量是否是一个实例 T
,您可以轻松地执行此操作:myVar is T
.
这种类型的inline
函数如下所示:reified
T
inline fun <reified T> myGenericFun()
Run Code Online (Sandbox Code Playgroud)
reified
工作您只能reified
与inline
功能组合使用.这样的函数使得编译器将函数的字节码复制到每个使用函数的地方(函数被"内联").当您使用reified类型调用内联函数时,编译器会知道用作类型参数的实际类型,并修改生成的字节码以直接使用相应的类.因此呼吁像myVar is T
变myVar is String
(如果类型参数是String
在字节码,并在运行时).
让我们看一个显示有用的示例reified
.我们想要为String
被调用创建一个扩展函数,toKotlinObject
它尝试将JSON字符串转换为具有函数泛型类型指定类型的普通Kotlin对象T
.我们可以使用com.fasterxml.jackson.module.kotlin
这个,第一种方法如下:
a)没有具体类型的第一种方法
fun <T> String.toKotlinObject(): T {
val mapper = jacksonObjectMapper()
//does not compile!
return mapper.readValue(this, T::class.java)
}
Run Code Online (Sandbox Code Playgroud)
该readValue
方法采用一种它应该解析JsonObject
为的类型.如果我们尝试获取Class
type参数T
,编译器会抱怨:"不能使用'T'作为reified类型参数.请改用类."
b)使用显式Class
参数进行解决方法
fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
val mapper = jacksonObjectMapper()
return mapper.readValue(this, c.java)
}
Run Code Online (Sandbox Code Playgroud)
作为一种解决方法,可以将Class
of of T
设为方法参数,然后将其用作参数readValue
.这是有效的,并且是通用Java代码中的常见模式.它可以如下调用:
data class MyJsonType(val name: String)
val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)
Run Code Online (Sandbox Code Playgroud)
c)Kotlin方式: reified
使用inline
带有reified
类型参数T
的函数可以以不同方式实现该函数:
inline fun <reified T: Any> String.toKotlinObject(): T {
val mapper = jacksonObjectMapper()
return mapper.readValue(this, T::class.java)
}
Run Code Online (Sandbox Code Playgroud)
有没有需要采取Class
的T
额外,T
可作为如果它是一个普通类.对于客户端,代码如下所示:
json.toKotlinObject<MyJsonType>()
Run Code Online (Sandbox Code Playgroud)
带有reified
类型的内联函数不能从Java代码中调用.
Yog*_*ity 13
reified
类型泛型
在 Kotlin 中使用泛型时,我们可以对任何类型的值执行操作T
:
fun <T> doSomething(value: T) {
println("Doing something with value: $value") // OK
}
Run Code Online (Sandbox Code Playgroud)
这里我们隐式调用toString()
了value
和的函数。
但是我们不能T
直接对类型进行任何操作:
fun <T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // Error
}
Run Code Online (Sandbox Code Playgroud)
让我们了解这个错误的原因。
类型擦除
在上面的代码中,编译器给出了一个错误:Cannot use 'T' as reified type parameter. Use a class instead.
这是因为在编译时,编译器从函数调用中删除了类型参数。
例如,如果您将函数调用为:
doSomething<String>("Some String")
Run Code Online (Sandbox Code Playgroud)
编译器删除了类型参数部分<String>
,运行时剩下的就是:
doSomething("Some String")
Run Code Online (Sandbox Code Playgroud)
这称为类型擦除。因此,在运行时(在函数定义内部),我们不可能确切知道T
代表哪种类型。
Java解决方案
Java 中这种类型擦除问题的解决方案是传递一个额外的参数,用Class
(在 Java 中)或KClass
(在 Kotlin 中)指定类型:
fun <T: Any> doSomething(value: T, type: KClass<T>) {
println("Doing something with type: ${type.simpleName}") // OK
}
Run Code Online (Sandbox Code Playgroud)
这样我们的代码就不会受到类型擦除的影响。但是这个解决方案很冗长而且不是很优雅,因为我们必须声明它并用一个额外的参数调用它。此外,指定类型绑定Any
是强制性的。
类型具体化
上述问题的最佳解决方案是 Kotlin 中的类型具体化。所述reified
类型参数之前改性剂能够在运行时被保持的类型的信息:
inline fun <reified T> doSomething(value: T) {
println("Doing something with type: ${T::class.simpleName}") // OK
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,多亏了reified
type 参数,我们在对 type 执行操作时不再出现错误T
。让我们看看inline
函数如何使这种魔法成为可能。
inline
职能
当我们将一个函数标记为 时inline
,编译器会在inline
调用该函数的任何地方复制该函数的实际主体。由于我们将doSomething()
函数标记为 an inline
,因此以下代码:
fun main() {
doSomething<String>("Some String")
}
Run Code Online (Sandbox Code Playgroud)
被编译为:
fun main() {
println("Doing something with type: ${String::class.simpleName}")
}
Run Code Online (Sandbox Code Playgroud)
因此,上面显示的两个代码片段是等效的。
在复制inline
函数体时,编译器还会用T
在函数调用中指定或推断的实际类型参数替换类型参数。例如,注意如何T
用实际类型参数替换类型参数String
。
reified
类型reified
类型参数的主要目标是知道类型参数T
在运行时表示的确切类型。
假设我们有一个不同类型水果的列表:
val fruits = listOf(Apple(), Orange(), Banana(), Orange())
Run Code Online (Sandbox Code Playgroud)
我们希望Orange
在一个单独的列表中过滤所有类型,如下所示:
val oranges = listOf(Orange(), Orange())
Run Code Online (Sandbox Code Playgroud)
没有 reified
为了过滤水果类型,我们可以编写一个扩展函数,List<Any>
如下所示:
fun <T> List<Any>.filterFruit(): List<T> {
return this.filter { it is T }.map { it as T } // Error and Warning
}
Run Code Online (Sandbox Code Playgroud)
在此代码中,首先我们过滤类型,并且仅在元素的类型与给定的类型参数匹配时才采用元素。然后,我们投的每个元素给定的类型参数和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 }
}
Run Code Online (Sandbox Code Playgroud)
然后像下面这样调用它:
val oranges = fruits.filterFruit<Orange>()
Run Code Online (Sandbox Code Playgroud)
我展示了这个函数是为了更容易演示。为了过滤集合中的类型,已经有一个标准库函数filterIsInstance()
。此函数以类似的方式使用了inline
和reified
修饰符。你可以简单地调用它如下:
val oranges = fruits.filterIsInstance<Orange>()
Run Code Online (Sandbox Code Playgroud)
reified
参数作为参数传递所述reified
改性剂使得有可能为一个函数类型参数传递作为类型参数到具有另一功能reified
调节剂:
inline fun <reified T> doSomething() {
// Passing T as an argument to another function
doSomethingElse<T>()
}
inline fun <reified T> doSomethingElse() { }
Run Code Online (Sandbox Code Playgroud)
reified
类型有时类型参数可以是泛型类型。例如,List<String>
在函数调用中doSomething<List<String>>()
。由于具体化,可以知道整个类型:
inline fun <reified T> getGenericType() {
val type: KType = typeOf<T>()
println(type)
}
Run Code Online (Sandbox Code Playgroud)
这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);
Run Code Online (Sandbox Code Playgroud)
你得到 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
类型的本质。
小智 5
reified
是在编译时授予使用权限(T
在函数内部访问)。
例如:
inline fun <reified T:Any> String.convertToObject(): T{
val gson = Gson()
return gson.fromJson(this,T::class.java)
}
Run Code Online (Sandbox Code Playgroud)
使用:
val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
println(userObject.name)
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
18570 次 |
最近记录: |