有没有办法从
KType
获取 Kotlin java.lang.reflect.Type
?
背景:我正在编写一些代码,这些代码从类中获取属性/字段并将它们传递给方法(对于熟悉这些理论的人来说,请考虑 JUnit 4 理论)。因为我需要 Java 兼容性,所以我不能总是使用纯 Kotlin 反射,因为像
MyClass::class.memberProperties
这样的调用不适用于原始 Java 字段。
另一种选择是将我的
KType
转换为java.lang.reflect.Type
(通过.javaType
),但是KType
提供了一个方便的isSupertypeOf(...)
函数,允许在两个KTypes
之间进行比较,我还没有找到类似的Java 的功能 Type
.
非常粗略的示例:
Kotlin 代码:
class KotlinTestClass {
fun test(someParam: List<String>) {
someParam.forEach { println(it) }
}
class CompatibleTypes {
val exact: List<String> = listOf("foo")
val mutable: MutableList<String> = mutableListOf("bar")
val implementation: ImmutableList<String> = immutableListOf("baz")
}
class NonCompatibleTypes {
val wrongParam: List<Int> = listOf(42)
val wrongType: Map<String, String> = mapOf("hello" to "world")
}
}
Java代码:
public class JavaTestClass {
public static class CompatibleTypes {
public static final List<String> exact = List.of("foo");
public static final ImmutableList<String> implementation = ImmutableList.of("baz");
}
public static class NonCompatibleTypes {
public static final List<Integer> wrongParam = List.of(42);
public static final Map<String,String> wrongType = Map.of("hello", "world");
}
}
Kotlin 测试代码:
fun main(args: Array<String>) {
val parameterType = KotlinTestClass::class.functions.first { it.name == "test" }.parameters[1].type
println("Target: $parameterType")
println("--------------------------------------")
//These should all print "true"
println("Compatible Kotlin types:")
KotlinTestClass.CompatibleTypes::class.memberProperties.asSequence()
.map { it.returnType }
.forEach { println(" - ${parameterType.isSupertypeOf(it)} ($it)") }
println("--------------------------------------")
println("Compatible Java types:")
//These should all print "true"
//This code doesn't work because the fields aren't valid Kotlin properties
JavaTestClass.CompatibleTypes::class.memberProperties.forEach { println(it) }
JavaTestClass.CompatibleTypes::class.java.declaredFields.asSequence()
.map { it.kotlinProperty?.returnType }
.filterNotNull()
.forEach { println(" - ${parameterType.isSupertypeOf(it)} ($it)") }
println("--------------------------------------")
println("Non-Compatible Kotlin types:")
//These should all print "false"
KotlinTestClass.NonCompatibleTypes::class.memberProperties.asSequence()
.map { it.returnType }
.forEach { println(" - ${parameterType.isSupertypeOf(it)} ($it)") }
println("--------------------------------------")
println("Non-Compatible Java types:")
//These should all print "false"
//This code doesn't work because the fields aren't valid Kotlin properties
JavaTestClass.NonCompatibleTypes::class.java.declaredFields.asSequence()
.map { it.kotlinProperty?.returnType }
.filterNotNull()
.forEach { println(" - ${parameterType.isSupertypeOf(it)} ($it)") }
}
假设
Type
来自 Java 中的声明,以下适用于简单类、参数化类型、原始类型和数组类型。不过,可能有一些我没有处理的边缘情况。
这个想法基本上是检查
Type
可能是什么(Class
、ParameterizedType
、TypeVariable
等)并处理每种情况,并在需要时递归遍历“类型树”。
fun convert(type: Type) = when(type) {
is Class<*> -> when {
// we takeUnless it.isPrimitive here because primitve arrays are mapped to their own Kotlin classes, not Array<T>
type.isArray -> type.kotlin.createType(listOfNotNull(type.componentType?.takeUnless { it.isPrimitive }?.toKTypeProjection()))
else -> type.kotlin.createType(List(type.typeParameters.size) { KTypeProjection.STAR })
}
is ParameterizedType -> (type.rawType as Class<*>).kotlin.createType(
type.actualTypeArguments.map { it.toKTypeProjection() }
)
is GenericArrayType -> Array::class.createType(listOf(type.genericComponentType.toKTypeProjection()))
is TypeVariable<*> -> type.toKType()
else -> throw NotImplementedError()
}
fun Type.toKTypeProjection(): KTypeProjection = when (this) {
is WildcardType -> when {
// lowerBounds and upperBounds should contain at most one element in Java
lowerBounds.isNotEmpty() -> KTypeProjection(KVariance.IN, convert(lowerBounds[0]))
upperBounds.isNotEmpty() -> KTypeProjection(KVariance.OUT, convert(upperBounds[0]))
else -> KTypeProjection(KVariance.INVARIANT, convert(this))
}
else -> KTypeProjection(KVariance.INVARIANT, convert(this))
}
fun TypeVariable<*>.toKType() = when (val decl = this.genericDeclaration) {
is Class<*> -> decl.kotlin.typeParameters.first { it.name == this.name }.createType()
is Method -> decl.kotlinFunction!!.typeParameters.first { it.name == this.name }.createType()
else -> throw NotImplementedError()
}