不兼容的类型: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
问题:
- 为什么编译器不会为
IA
接口和ActionEvent
Java类引发错误? - 为什么Compiller为
A
班级提出错误? - 如何检查该函数是否有
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
,你试图检查,如果它是一个类的实例,或IA
或ActionEvent
,它不是。
所以你需要使用一个不同的运算符来检查它们是否相等,或者调用isAssignableFrom()
方法。 然后你需要检查你正在比较的两个东西是正确的数据类型,并做你的期望。
在另一个答案中,@迈克尔说,你可以只对一个Type
和Class
相同的平等,这并不总是正确的; 不是那么简单。 有时Type
是一个Class
但有时它是一个ParameterizedType
, GenericArrayType
, TypeVariable
或WildcardType
,它们不能与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。 这有点复杂。