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> 实例,无论 TV 是具体化的还是硬编码的。

但是,即使这是可能的,您的示例代码也没有意义,因为它检查具有该名称的类型是否是 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" 对我来说感觉很软弱,除非我至少了解一种在工作实施中实现它的方式。