简体   繁体   English

Kotlin reified类型参数不能用作函数体中的类型参数

[英]Kotlin reified type parameter can't be used as type parameter in body of function

A reified type parameter in Kotlin prevents type parameter erasure and allows the type parameter to be known at run-time. Kotlin中的reified类型参数可防止类型参数擦除,并允许在运行时知道type参数。 This allows the following code to compile and run as expected: 这允许以下代码按预期编译和运行:

inline fun <reified T> isA(value: Any) = value is T

However, when I try to use "T" as a type parameter instead of standalone I get a message that it is an erased type. 但是,当我尝试使用“T”作为类型参数而不是独立时,我得到一条消息,它是一个擦除类型。 This is demonstrated by the following code that is for illustrative purposes only : 以下代码证明了这一点,仅用于说明目的

inline fun <reified T> isListOfA(name: String): Boolean {
    val candidate = Class.forName(name)
    return candidate is List<T>
}

Is this due to a technical limitation? 这是由于技术限制吗? If so, what is that limitation? 如果是这样,那个限制是什么?

Evidently I did not formulate my question appropriately to get an answer of the form that I wanted. 显然我没有恰当地提出我的问题来得到我想要的形式的答案。 Most of the answers here are some variation of "because you can't do that in Java". 这里的大多数答案都是“因为你不能用Java做的”的一些变体。 Well, you cannot do x instanceof T in Java, either but you can do x is T in Kotlin. 好吧,你不能在Java中使用x instanceof T ,但你可以在Kotlin中做x is T I am looking for the underlying practical roadblock not the Java rule. 我正在寻找潜在的实际障碍,而不是Java规则。 Rules are made to be broken, after all. 毕竟,规则被打破了。

From my comment on the first answer here, the reformulated question is: if objectref is T can be made to work in Kotlin by some mechanism X why can't objectref is SomeClass<T> be made to work by that same mechanism? 从我对这里第一个答案的评论,重新提出的问题是:如果objectref is T可以通过某种机制X在Kotlin中工作,为什么不能使用objectref is SomeClass<T>通过相同的机制工作?

tl;dr answer: Because there will be no Class object for SomeClass<T> at run-time. tl; dr回答:因为在运行时没有SomeClass<T> Class对象。

Longer answer: First we must understand mechanism X , which is to generate an instanceof bytecode instruction for is T . 更长的答案:首先我们必须了解机制Xis T生成字节码指令的instanceof This instruction takes objectref and the name N of some class C , where N is determined from context by the compiler. 该指令采用objectref和某些类C的名称N ,其中N由编译器根据上下文确定。 At runtime, the class C derived from N will be used to evaluate the objectref is T expression. 在运行时,从N派生的类C将用于评估objectref is T表达式。 In order for this evaluation to occur the class object for C must be instantiated. 为了进行此评估,必须实例化C的类对象。 So to use this same mechanism for objectref is SomeClass<T> then N would be SomeClass<T> . 因此,对于objectref is SomeClass<T>使用相同的机制objectref is SomeClass<T>那么N将是SomeClass<T> Due to type erasure, there will not be a class object for SomeClass<T> so it is not possible to generate the needed instanceof instruction and thereby apply the same mechanism. 由于类型擦除, SomeClass<T>不会有类对象,因此无法生成所需的instanceof指令,从而应用相同的机制。 In addition, the instanceof instruction cannot take a name of the form SomeClass<T> . 此外, instanceof指令不能采用SomeClass<T>形式的名称。 Therefore, if objectref is SomeClass<T> is to work, some other mechanism Y must be found and implemented in Kotlin. 因此,如果objectref is SomeClass<T>要工作,则必须在Kotlin中找到并实现其他一些机制Y Such a mechanism may or may not exist. 这种机制可能存在也可能不存在。

I know that some may say that this is the same thing as some of the other answers. 我知道有些人可能会说这与其他一些答案是一回事。 However, for better or worse my learning style is to understand how things work down on the metal, and then synthesize this against the abstract model. 然而,无论好坏,我的学习方式是了解事物如何在金属上起作用,然后将其与抽象模型相结合。 In this case is the Java Generics notion of erasure is the abstract model (or part of it). 在这种情况下,Java Generics擦除概念是抽象模型(或其一部分)。 Really, "erasure" feels squishy to me unless I understand at least one way that it is realized in a working implementation. 真的,“擦除”对我来说感觉很软弱,除非我至少了解它在工作实现中实现的一种方式。

The technical limitation that prevents you from doing that is generics type erasure on JVM . 阻止您这样做的技术限制是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. 基本上,在运行时,泛型类型List<T>对象只是一个与对象一起工作的List :它只在编译时才检查类型安全性的赋值和函数调用。 The actual type parameter T is there only during compile time and then gets erased. 实际类型参数T仅在编译期间存在,然后被擦除。 It cannot be restored at runtime (at least for now: there is Project Valhalla that might introduce runtime reified generics for JVM one day). 它无法在运行时恢复(至少目前为止:有一天Project Valhalla可能会为JVM引入运行时验证的泛型)。

In a non-inline Kotlin function (and with a non-reified type parameter), you could not even do the first kind of the check, value is T , because an ordinary type parameter would get erased as well. 在非内联Kotlin函数(以及非reified类型参数)中,您甚至无法进行第一种检查, value is T ,因为普通类型参数也会被删除。

With reified type parameters, the function body gets inlined at its call sites, with the actual (or inferred) type parameter substituted for T : when you call isA<String>("abc") , the call site will have the bytecode with the instanceof check for String . 使用reified类型参数,函数体在其调用站点内联,实际(或推断)类型参数替换为T :当您调用isA<String>("abc") ,调用站点将具有字节码instanceof检查String

But even with reified type parameters, you cannot introspect the generic types: you can check that something is List<*> but not that something is List<String> : the type argument is not stored anywhere at runtime. 但是即使使用了reified类型参数,也无法对内部类型进行内省:您可以检查something is List<*>但不是something is List<String> :类型参数不会存储在运行时的任何位置。

Also note that isA<List<String>>(listOf(1, 2, 3)) will return true . 另请注意isA<List<String>>(listOf(1, 2, 3))将返回true That's how this odd case is handled in Kotlin: only the non-generic part of the type can be actually checked at runtime, and so it is. 这就是Kotlin处理这个奇怪的情况:只有该类型的非泛型部分可以在运行时实际检查,所以它是。

there is no way to do that in Kotlin since Java erase the generic type parameter T into Object /upper-bounded type at compile-time. 因为Java在编译时将泛型类型参数T擦除为Object / upper-bounded类型,所以在Kotlin中无法做到这一点。

The first approach can working it is because value is T is inlined into call-site function with reified type, for example: 第一种方法可以工作,因为value is T被内联到具有reified类型的call-site函数,例如:

//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

Parameterized types are always erased at runtime. 参数化类型总是在运行时擦除。 So you can check that a value is a T instance but not a T<V> instance, no matter if T and V are reified or hard-coded. 因此,您可以检查值是否是T实例但不是T<V>实例,无论TV是否已实现或硬编码。

However, even if that was possible, your example code does not make sense because it checks if the type with that name is an instance of List, instead of checking if the type with that name is the expected List type. 但是,即使可能,您的示例代码也没有意义,因为它检查具有该名称的类型是否是List的实例 ,而不是检查具有该名称的类型是否是预期的List类型。

If you have an instance of an object and want to check that it's a List only containing items of the expected type, you can still write something like this: 如果你有一个对象的实例 ,并想要检查它是否只包含期望类型的项目,你仍然可以这样写:

inline fun <reified T> isListOfA(instance: Any)
    = instance is List<*> && instance.all { it is T }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM