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

时间:2016-04-24 15:38:27

标签: kotlin

我想检查该函数是否具有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类型的参数?

2 个答案:

答案 0 :(得分:5)

问题是你要检查KType是否Afalse始终是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.") } - 也没有编译错误。

您应该做些什么来检查参数类型是某个类还是某个接口,如下所示。

{{1}}

答案 1 :(得分:2)

首先,您使用的是错误的运算符。 is检查某些事物的实例是否为给定类。你没有实例。您改为使用KType来检查它是AIA还是ActionEvent的实例,而不是。{/ p >

因此,您需要使用不同的运算符,即检查它们是否相等或调用方法isAssignableFrom()。然后你需要检查你正在比较的两件事是正确的数据类型并做你期望的事情。

在另一个答案中,@迈克尔说你可以将TypeClass视为平等,但并非总是如此;不那么简单。有时TypeClass,但有时它是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>的参数是否匹配{{1 (FAL !!!)。或者,如果您尝试比较数组类型,则再次匹配。

要修复此泛型问题,我们还需要删除您正在检查的A::class.defaultType。我们需要一个辅助函数来做到这一点。

以下是从Klutter library复制的paramter.type,并删除了一般内容以生成KType。您将需要KClass依赖项才能使用此代码。 您可以通过仅使用Java kotlin-reflect而不是直接在任何地方使用kotlin-refect来删除Class依赖项。其他一些代码将不得不改变。

使用以下扩展功能:

KClass

您现在可以更简单地编写代码:

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

然后fun assignCheck(ancestor: KClass<*>, checkType: KType): Boolean = ancestor.java.isAssignableFrom(checkType.javaType.erasedType()) 表达式更改为:

when

额外的功劳,比较完整的泛型:

为了比较完整的泛型,我们需要将所有内容转换为我们可以比较但仍具有泛型的内容。最简单的方法是将所有内容都放入Java 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.") } ,因为很难将所有内容都放入Type。首先我们需要一个KType类型的类,我们也会从Klutter library中窃取它:

TypeReference

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

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 表达式可以更详细地使用泛型:

when

但是在这样做的时候,我们再次破坏了继承检查。我们并没有真正检查泛型的协方差(他们自己可以进行超类检查)。

Double Extra Credit,继承和泛型怎么样?!?

嗯,这不是那么有趣。我将不得不考虑一下这个,也许以后可以把它添加到Klutter。这有点复杂。