Kotlin 具体化类型参数不能用作函数体中的类型参数
Kotlin reified type parameter can't be used as type parameter in body of function
Kotlin 中具体化的类型参数可防止类型参数擦除,并允许在 运行 时知道类型参数。这允许以下代码按预期编译和运行:
inline fun <reified T> isA(value: Any) = value is T
但是,当我尝试使用 "T" 作为类型参数而不是独立类型时,我收到一条消息,指出它是已擦除的类型。以下代码对此进行了演示 ,仅供说明之用:
inline fun <reified T> isListOfA(name: String): Boolean {
val candidate = Class.forName(name)
return candidate is List<T>
}
这是技术限制吗?如果是这样,那个限制是什么?
在 Kotlin 中无法做到这一点,因为 Java 在编译时将泛型类型参数 T
擦除为 Object
/上限类型。
第一种方法可以工作是因为value is T
被内联到具有具体化类型的调用站点函数中,例如:
//val is_string = isA<String>(1) // inline into the call-site function as below:
val i:Int = 1
// v--- the actual type argument is inlined here
val is_string = 1 is String
阻止您这样做的技术限制是 generics type erasure on JVM. Basically, at runtime an object of a generic type List<T>
becomes just a List
that works with objects: it's only at compile-time that the type safety is checked for assignments and function calls. The actual type parameter T
is there only during compile time and then gets erased. It cannot be restored at runtime (at least for now: there is Project Valhalla 可能有一天会为 JVM 引入运行时具体化的泛型)。
在非内联 Kotlin 函数中(以及非具体化类型参数),您甚至无法进行第一种检查,value is T
,因为普通类型参数会被删除为出色地。
使用具体化的类型参数,函数体在其调用点内联,用实际(或推断的)类型参数代替 T
:当您调用 isA<String>("abc")
时,调用点将让带有 instanceof
的字节码检查 String
。
但即使使用具体化的类型参数,您也无法检查泛型类型:您可以检查 something is List<*>
但不能检查 something is List<String>
:类型参数不会在运行时存储在任何地方。
另请注意,isA<List<String>>(listOf(1, 2, 3))
将 return true
。这就是 Kotlin 中处理这种奇怪情况的方式:在运行时只能实际检查类型的非泛型部分,事实也是如此。
参数化类型总是在运行时被擦除。因此,您可以检查一个值是 T
实例而不是 T<V>
实例,无论 T
和 V
是具体化的还是硬编码的。
但是,即使这是可能的,您的示例代码也没有意义,因为它检查具有该名称的类型是否是 List 的 实例 ,而不是检查是否具有该名称的类型是预期的列表类型。
如果您有一个对象的 实例 并且想要检查它是否是一个仅包含预期类型的项目的列表,您仍然可以这样写:
inline fun <reified T> isListOfA(instance: Any)
= instance is List<*> && instance.all { it is T }
显然我没有适当地表述我的问题来得到我想要的形式的答案。这里的大部分答案都是 "because you can't do that in Java" 的一些变体。好吧,你也不能在 Java 中执行 x instanceof T
,但你可以在 Kotlin 中执行 x is T
。我正在寻找潜在的实际障碍而不是 Java 规则。毕竟规则是用来打破的。
根据我对此处第一个答案的评论,重新表述的问题是:如果 objectref is T
可以通过某种机制在 Kotlin 中工作 X
为什么不能 objectref is SomeClass<T>
通过相同的机制工作?
tl;dr 回答:因为 SomeClass<T>
在 运行 时间没有 Class
对象。
更长的答案:首先我们必须了解机制X
,即为is T
生成instanceof
字节码指令。该指令采用 objectref
和某些 class C
的名称 N
,其中 N
由编译器根据上下文确定。在 运行 时,从 N
派生的 class C
将用于计算 objectref is T
表达式。为了进行此评估,必须实例化 C
的 class 对象。因此,要对 objectref is SomeClass<T>
使用相同的机制,那么 N
将是 SomeClass<T>
。由于类型擦除,SomeClass<T>
不会有 class 对象,因此无法生成所需的 instanceof
指令,从而无法应用相同的机制。此外,instanceof
指令不能采用 SomeClass<T>
形式的名称。因此,如果 objectref is SomeClass<T>
起作用,则必须在 Kotlin 中找到并实施其他一些机制 Y
。这样的机制可能存在也可能不存在。
我知道有些人可能会说这与其他一些答案是一回事。然而,无论好坏,我的学习风格是了解事物在金属上的工作原理,然后将其与抽象模型进行综合。在这种情况下,Java 擦除的泛型概念是抽象模型(或其中的一部分)。真的,"erasure" 对我来说感觉很软弱,除非我至少了解一种在工作实施中实现它的方式。
Kotlin 中具体化的类型参数可防止类型参数擦除,并允许在 运行 时知道类型参数。这允许以下代码按预期编译和运行:
inline fun <reified T> isA(value: Any) = value is T
但是,当我尝试使用 "T" 作为类型参数而不是独立类型时,我收到一条消息,指出它是已擦除的类型。以下代码对此进行了演示 ,仅供说明之用:
inline fun <reified T> isListOfA(name: String): Boolean {
val candidate = Class.forName(name)
return candidate is List<T>
}
这是技术限制吗?如果是这样,那个限制是什么?
在 Kotlin 中无法做到这一点,因为 Java 在编译时将泛型类型参数 T
擦除为 Object
/上限类型。
第一种方法可以工作是因为value is T
被内联到具有具体化类型的调用站点函数中,例如:
//val is_string = isA<String>(1) // inline into the call-site function as below:
val i:Int = 1
// v--- the actual type argument is inlined here
val is_string = 1 is String
阻止您这样做的技术限制是 generics type erasure on JVM. Basically, at runtime an object of a generic type List<T>
becomes just a List
that works with objects: it's only at compile-time that the type safety is checked for assignments and function calls. The actual type parameter T
is there only during compile time and then gets erased. It cannot be restored at runtime (at least for now: there is Project Valhalla 可能有一天会为 JVM 引入运行时具体化的泛型)。
在非内联 Kotlin 函数中(以及非具体化类型参数),您甚至无法进行第一种检查,value is T
,因为普通类型参数会被删除为出色地。
使用具体化的类型参数,函数体在其调用点内联,用实际(或推断的)类型参数代替 T
:当您调用 isA<String>("abc")
时,调用点将让带有 instanceof
的字节码检查 String
。
但即使使用具体化的类型参数,您也无法检查泛型类型:您可以检查 something is List<*>
但不能检查 something is List<String>
:类型参数不会在运行时存储在任何地方。
另请注意,isA<List<String>>(listOf(1, 2, 3))
将 return true
。这就是 Kotlin 中处理这种奇怪情况的方式:在运行时只能实际检查类型的非泛型部分,事实也是如此。
参数化类型总是在运行时被擦除。因此,您可以检查一个值是 T
实例而不是 T<V>
实例,无论 T
和 V
是具体化的还是硬编码的。
但是,即使这是可能的,您的示例代码也没有意义,因为它检查具有该名称的类型是否是 List 的 实例 ,而不是检查是否具有该名称的类型是预期的列表类型。
如果您有一个对象的 实例 并且想要检查它是否是一个仅包含预期类型的项目的列表,您仍然可以这样写:
inline fun <reified T> isListOfA(instance: Any)
= instance is List<*> && instance.all { it is T }
显然我没有适当地表述我的问题来得到我想要的形式的答案。这里的大部分答案都是 "because you can't do that in Java" 的一些变体。好吧,你也不能在 Java 中执行 x instanceof T
,但你可以在 Kotlin 中执行 x is T
。我正在寻找潜在的实际障碍而不是 Java 规则。毕竟规则是用来打破的。
根据我对此处第一个答案的评论,重新表述的问题是:如果 objectref is T
可以通过某种机制在 Kotlin 中工作 X
为什么不能 objectref is SomeClass<T>
通过相同的机制工作?
tl;dr 回答:因为 SomeClass<T>
在 运行 时间没有 Class
对象。
更长的答案:首先我们必须了解机制X
,即为is T
生成instanceof
字节码指令。该指令采用 objectref
和某些 class C
的名称 N
,其中 N
由编译器根据上下文确定。在 运行 时,从 N
派生的 class C
将用于计算 objectref is T
表达式。为了进行此评估,必须实例化 C
的 class 对象。因此,要对 objectref is SomeClass<T>
使用相同的机制,那么 N
将是 SomeClass<T>
。由于类型擦除,SomeClass<T>
不会有 class 对象,因此无法生成所需的 instanceof
指令,从而无法应用相同的机制。此外,instanceof
指令不能采用 SomeClass<T>
形式的名称。因此,如果 objectref is SomeClass<T>
起作用,则必须在 Kotlin 中找到并实施其他一些机制 Y
。这样的机制可能存在也可能不存在。
我知道有些人可能会说这与其他一些答案是一回事。然而,无论好坏,我的学习风格是了解事物在金属上的工作原理,然后将其与抽象模型进行综合。在这种情况下,Java 擦除的泛型概念是抽象模型(或其中的一部分)。真的,"erasure" 对我来说感觉很软弱,除非我至少了解一种在工作实施中实现它的方式。