简体   繁体   中英

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. 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. 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". Well, you cannot do x instanceof T in Java, either but you can do x is T in Kotlin. I am looking for the underlying practical roadblock not the Java rule. 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?

tl;dr answer: Because there will be no Class object for SomeClass<T> at run-time.

Longer answer: First we must understand mechanism X , which is to generate an instanceof bytecode instruction for is T . This instruction takes objectref and the name N of some class C , where N is determined from context by the compiler. At runtime, the class C derived from N will be used to evaluate the objectref is T expression. In order for this evaluation to occur the class object for C must be instantiated. So to use this same mechanism for objectref is SomeClass<T> then N would be 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. In addition, the instanceof instruction cannot take a name of the form SomeClass<T> . Therefore, if objectref is SomeClass<T> is to work, some other mechanism Y must be found and implemented in Kotlin. 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). 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 . 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 that might introduce runtime reified generics for JVM one day).

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.

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 .

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.

Also note that isA<List<String>>(listOf(1, 2, 3)) will return 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.

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.

The first approach can working it is because value is T is inlined into call-site function with reified type, for example:

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

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.

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 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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