不兼容的类型:A和kotlin.reflect.KType

我想检查该函数是否具有A类型的参数使用以下代码:

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

但是当我尝试编译它时,我看到以下错误:

 > Error:(20, 19) Incompatible types: A and kotlin.reflect.KType 

问题:

  1. 为什么编译器不会为IA接口和ActionEvent Java类引发错误?
  2. 为什么Compiller为A班级提出错误?
  3. 如何检查该函数是否有A类型的参数?

问题是你试图检查KType是否是A ,这总是false 。 编译器知道它并引发编译错误。 但是IA是一个接口一个实现KType的类也可以实现这个接口,所以没有编译错误。 ActionEvent是一个开放的类,所以它的子类型可以实现KType – 也没有编译错误。

你应该做什么来检查参数类型是否是某个类或某个接口如下。

 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.") } 

首先,你正在使用错误的操作符。 is检查某个东西的实例是否是给定的类。 你没有实例。 你反而有一个KType ,你试图检查,如果它是一个类的实例,或IAActionEvent ,它不是。

所以你需要使用一个不同的运算符来检查它们是否相等,或者调用isAssignableFrom()方法。 然后你需要检查你正在比较的两个东西是正确的数据类型,并做你的期望。

在另一个答案中,@迈克尔说,你可以只对一个TypeClass相同的平等,这并不总是正确的; 不是那么简单。 有时Type是一个Class但有时它是一个ParameterizedTypeGenericArrayTypeTypeVariableWildcardType ,它们不能与equals进行比较。 所以这种方法是错误的,如果你有一个参数的方法,使用泛型它打破。

这是一个不支持泛型的版本,如果在参数中使用泛型,它们将不匹配。 这也比较KType使用相等,这意味着它不适用于超类或接口匹配的继承类。 但最简单的是:

  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.") } 

例如,如果类A具有泛型参数T所以你想检查一个参数是A<String>A<Monkey>你将不会匹配A::class.defaultType (FALSE !!!)。 或者如果您尝试比较数组类型,则再次不匹配。

为了解决这个泛型的问题,我们还需要擦除正在检查的paramter.type类型。 我们需要一个辅助函数来做到这一点。

这里是从Klutter库复制一个KType ,并删除泛型制作一个KClass 。 你将需要kotlin-reflect依赖来使用这个代码。 你可以通过只使用Java Class而不是直接在任何地方使用KClass来移除kotlin-refect依赖。 其他一些代码将不得不改变。

具有以下扩展功能:

 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.") } } 

您现在可以更简单地编写代码,如下所示:

  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.") } 

所以泛型被忽略了,这就比较了原始擦除类相互之间的作用; 但又没有继承。

为了支持继承,你可以使用这个稍微修改过的版本。 when表达式和辅助函数需要不同的形式when

 fun assignCheck(ancestor: KClass<*>, checkType: KType): Boolean = ancestor.java.isAssignableFrom(checkType.javaType.erasedType()) 

然后, when表达式改为:

 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.") } 

额外的功劳,比较完整的仿制药:

为了比较完整的泛型,我们需要将所有东西都转换成我们可以比较的东西,但仍然有泛型。 最简单的方法是将所有东西都变成Java Type因为把所有东西都变成KType更困难。 首先,我们需要一个TypeReference类型的类,我们也会从Klutter库中窃取它:

 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] } } } 

现在使用这个快速扩展方法:

 inline fun <reified T: Any> ft(): Type = object:TypeReference<T>(){}.type 

然后我们的表达可以用泛型更详细的表达:

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

但是在这样做的时候,我们又打破了继承检查。 而且我们并不真正检查泛型的协变性(它们本身可能有超类检查)。

Double Extra Credit,继承和泛型又如何呢?

呃,这并不好玩。 我将不得不考虑这一点,也许稍后再将它添加到Klutter。 这有点复杂。