Reputation: 9981
I want to check that function has parameter of A
type use the following code:
import kotlin.reflect.*
import javafx.event.ActionEvent
interface IA {}
class A {}
class B {
fun test(a: A, ia: IA, event: ActionEvent) {
println(a)
println(ia)
println(event)
}
}
fun main(args: Array<String>) {
for (function in B::class.declaredMemberFunctions) {
for (parameter in function.parameters) {
when (parameter.type) {
is IA -> println("Has IA interface parameter.")
is ActionEvent -> println("Has ActionEvent class parameter.")
is A -> println("Has A class parameter.") // <---- compilation error in this line
}
}
}
}
but when I try to compile it I see the following error:
> Error:(20, 19) Incompatible types: A and kotlin.reflect.KType
Questions:
IA
interface and ActionEvent
Java class?A
class?A
type?Upvotes: 3
Views: 3282
Reputation: 86006
First, you are using the wrong operator. is
checks if an instance of something is a given class. You have no instance. You instead have a KType
that you are trying to check if it is the instance of a class A
, or IA
or ActionEvent
, it isn't.
So you need to use a different operator, which is to check if they are equal or call the method isAssignableFrom()
. And then you need to check that the two things you are comparing are the right datatypes and do what you expect.
In another answer, @Michael says you can just treat a Type
and Class
the same for equality, that isn't always true; not that simple. Sometimes a Type
is a Class
but sometimes it is a ParameterizedType
, GenericArrayType
, TypeVariable
, or WildcardType
which are not comparable with equals. So that approach is wrong if you ever have a parameter to the method that uses generics it breaks.
Here is a version that does not support generics in that if generics are used in the parameter, they will not match. This also compares KType
using equality, which means it does not work for inherited classes matching against a superclass or interface. But the most simple is:
when (parameter.type) {
IA::class.defaultType -> println("Has IA interface parameter.")
ActionEvent::class.defaultType -> println("Has ActionEvent class parameter.")
A::class.defaultType -> println("Has A class parameter.")
}
This breaks if For example if class A
had generic parameter T
so you wanted to check a parameter that is A<String>
or A<Monkey>
you will not match A::class.defaultType
(FALSE!!!). Or if you tried to compare array types, again will not match.
To fix this generics problem, we need to also erase the paramter.type
you are checking. We need a helper function to do that.
Here is one copied from the Klutter library that takes a KType
and erases the generics to make a KClass
. You will need the kotlin-reflect
dependency to use this code. You can remove the kotlin-refect
dependency by only working with Java Class
and not using KClass
anywhere directly. Some other code will have to change.
With the following extension function:
fun KType.erasedType(): KClass<*> {
return this.javaType.erasedType().kotlin
}
@Suppress("UNCHECKED_CAST")
fun Type.erasedType(): Class<*> {
return when (this) {
is Class<*> -> this as Class<Any>
is ParameterizedType -> this.getRawType().erasedType()
is GenericArrayType -> {
// getting the array type is a bit trickier
val elementType = this.getGenericComponentType().erasedType()
val testArray = java.lang.reflect.Array.newInstance(elementType, 0)
testArray.javaClass
}
is TypeVariable<*> -> {
// not sure yet
throw IllegalStateException("Not sure what to do here yet")
}
is WildcardType -> {
this.getUpperBounds()[0].erasedType()
}
else -> throw IllegalStateException("Should not get here.")
}
}
You can now write your code more simply as:
when (parameter.type.erasedType()) {
IA::class-> println("Has IA interface parameter.")
ActionEvent::class -> println("Has ActionEvent class parameter.")
A::class -> println("Has A class parameter.")
}
So generics are ignored and this works comparing the raw erased class against each other; but again without inheritance.
To support inheritance you can use this version slightly modified. You need a different form of when
expression and a helper function:
fun assignCheck(ancestor: KClass<*>, checkType: KType): Boolean =
ancestor.java.isAssignableFrom(checkType.javaType.erasedType())
Then the when
expression changed to:
when {
assignCheck(IA::class, parameter.type) -> println("Has IA interface parameter.")
assignCheck(ActionEvent::class, parameter.type) -> println("Has ActionEvent class parameter.")
assignCheck(A::class, parameter.type) -> println("Has A class parameter.")
}
To compare full generics we need to convert everything to something that we can compare that still has generics. The easiest is to get everything into a Java Type
since it is harder to get everything into a KType
. First we need a TypeReference
type class, we'll steal this from Klutter library as well:
abstract class TypeReference<T> protected constructor() {
val type: Type by lazy {
javaClass.getGenericSuperclass().let { superClass ->
if (superClass is Class<*>) {
throw IllegalArgumentException("Internal error: TypeReference constructed without actual type information")
}
(superClass as ParameterizedType).getActualTypeArguments()[0]
}
}
}
Now a quick extension method to use this:
inline fun <reified T: Any> ft(): Type = object:TypeReference<T>(){}.type
And then our when
expression can be more detailed with generics:
for (parameter in function.parameters) {
when (parameter.type.javaType) {
ft<IA>() -> println("Has IA interface parameter.")
ft<ActionEvent>() -> println("Has ActionEvent class parameter.")
ft<A<String>>() -> println("Has A<String> class parameter.") // <---- compilation error in this line
ft<A<Monkey>>() -> println("Has A<Monkey> class parameter.") // <---- compilation error in this line
}
}
But in doing this, we broke inheritance checking again. And we don't really check covariance of the generics (they themselves could have superclass checks).
Um, this isn't so much fun. I'll have to think about that one a bit, maybe add it to Klutter later. It is kinda complicated.
Upvotes: 2
Reputation: 54725
The thing is you're trying to check if KType
is A
, which is always false
. And the compiler knows it and raises a compilation error. But IA
is an interface a class that implements KType
can possibly implement this interface too so there's no compilation error. ActionEvent
is an open class so it's subtype can implement KType
- no compilation error either.
What you should do to check if the parameter type is some class or some interface is the following.
when (parameter.type.javaType) {
IA::class.javaClass -> println("Has IA interface parameter.")
ActionEvent::class.javaClass -> println("Has ActionEvent class parameter.")
A::class.javaClass -> println("Has A class parameter.")
}
Upvotes: 6