Currently passing a class type to a generic function provides more capabilities than using a reified function.
For example
fun process(type: Class) {…}
Can be called using any random java.lang.Class object.
Whereas
inline fun process() {}
Can only be called with a specific class e.g. process() but you can’t use a class that you retrieved from a function or an object e.g. this doesn’t work.
val type = randomObject::class.java
process()
You could make your function work with both approaches:
private inline fun <reified T> process(clazz: Class<T> = T::class.java) = println(clazz)
fun main() {
process<String>()
val clazz = List::class.java
process(clazz)
}
Or, in case you cannot modify the original reified
function, you could add a simple wrapper function:
private inline fun <reified T> process() = println(T::class.java) // original function
private inline fun <reified T> process(clazz: Class<T>) = process<T>()
fun main() {
process<String>()
val clazz = List::class.java
process(clazz)
}
However, in any place where you can write val type = SomeClass::class.java
you can write as well process<SomeClass>()
, so I’d be happy to learn more about your specific use case.
That is if you already know the classs you are going to use.
But if you aren’t using a specific class i.e. you are getting the class from a function or from an object’s type then you can’t call the reified function unless I guess you use the workaround you specified but that requires duplating the function.
For example you can’t do this.
val type = getRandomType()
process()
I wanted to propose that reified functions be able to accept variables containing classes as their type argument in a future version of kotlin.
Oh yeah and your workaround causes an error.
Cannot use ‘CapturedType(out classname)’ as reified type parameter.
Using reified is useful when you know at compile time what class you are gonna use.
But if the class is only known at runtime then only the old way of passing the class as an argument will work.
If I understand correctly, reified types are meant to make generics easier and safer to use. All this happens at compile-time.
Now, if you want to work with arbitrary class objects, then you"re working with reflection at runtime, and, unless I misunderstand (which is still possible), I’d say that’s not the aim of reified types, because generics are replaced upon compilation.
Now, maybe I miss a point, and you are welcome to further describe how you think reified types could be improved.
Notes:
- Both examples provided by @akurczak are working on 1.4.30 JVM playground: Kotlin Playground: Edit, Run, Share Kotlin Code Online. What version have you tested on ?
- To help mutual understanding, can you try to post both formatted and valid code please ?
Yeah sorry.
I tried implementing his workaround but I guess I must have done something wrong.
I tried reimplementing my example with his solution on the play ground and it worked fine.
Sorry about that.
But anyway it is still a workaround.
A workaround that requires another function for each reified function.
What I am suggesting is that for reified functions to also accept classes stored in variables as their type arguments.
This should be feasible.
Generic functions that accept a class as an argument currently support this use case.
They just take the class in the variable as an argument and type argument is infered from the class.
What I am suggesting is that for reified functions to also accept classes stored in variables as their type arguments.
This should be feasible.
That is impossible. The reason why functions with reified type parameters has to be declared inline
is because the type parameter itself is inlined as well. Since this is done at compile time, it requires the actual type parameter to be known at compile time as well.
The fact that the type is inlined at compile time also means that there are things that you can do with reified type parameters that you cannot do with a class object. Most notably, you have access to the full type, including nested parameters, whereas with a class object, you only have the erased type. Example:
import java.lang.reflect.*
interface GenericInterface<T>
inline fun <reified T> typeName(): String {
val gi = object: GenericInterface<T> {}
return (gi::class.java.genericInterfaces[0] as ParameterizedType).actualTypeArguments[0].typeName
}
fun main() {
println(typeName<List<String>>())
}
Notice that String
is part of the output. That would not be possible if you only had a class object available.
Yeah passing it as a class erases the type arguments.
I tried this.
import java.lang.reflect.*
interface GenericInterface
inline fun typeName(): String {
val gi = object: GenericInterface {}
return (gi::class.java.genericInterfaces[0] as ParameterizedType).actualTypeArguments[0].typeName
}
inline fun typeName(type: Class) {
typeName()
}
fun main() {
println(typeName(List::class.java))
}
But there are use cases including mine where this isn’t a problem.
Where you don’t use reflection to access the type aruments.
So couldn’t there be a trade off where if the dev supplies the class directly no type erasure happens whereas if the dev uses a variable to supply the class then the type is somewhat erased.