如何在 Kotlin 中安全地投射反射 class

How to safely cast a reflected class in Kotlin

我需要在 Kotlin 运行时动态加载 classes。我想检查他们是否实现了我的接口,如果是的话,全部都是绿色的。不幸的是,Kotlin 的 "smart casts" 让我失望了:

var className = "some.class.Name"
val unsafeClass = Class.forName(className).kotlin
require(unsafeClass.isSubclassOf(MyInterface::class)) {
    "Class '$className' is not a MyInterface"
}
val safeClass = unsafeClass as KClass<MyInterface>
                            ^^^^^^^^^^^^^^^^^^^^^^
                            Unchecked cast: KClass<out Any!> to KClass<MyInterface>

我正在清楚地检查 class 是否实现了给定的接口。我可以重新措辞此代码以避免警告吗?

我尝试使用 is KClass<MyInterface> 进行测试,但出现类型擦除错误(很明显,因为通用类型信息在运行时消失了。)


编辑:澄清一下,我的应用程序需要在启动时和配置期间读取 class 名称 "some.class.Name";加载那些 classes;检查它们是否满足接口;并存储一个 Class 或 KClass 参考以备后用。在运行时,它将使用这些引用来创建对象,使用 cls.createInstance() 或类似的。

我的问题:有什么方法可以在不收到不安全转换警告的情况下这样做吗?

我可以在配置时收到警告,当我将 KClass<*> 转换为 KClass<MyInterface> 时(即使我 required class 是一个子class) 但后来我没有收到任何警告,因为 .createInstance()KClass<MyInterface> class 上引用 returns 类型检查的 MyInterface 实例。

或者,我可以将引用存储为 KClass<*>,而不会在配置时发出警告,但随后我会在创建实例的地方收到警告,因为我需要不安全的转换Object 个实例到 MyInterface

有没有满足编译器的解决方案?

JVM 和 Kotlin 仅在编译器级别实现泛型。在运行时看不到泛型 class 的泛型参数。 https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

在运行时,Class<*>Class<MyInterface> 没有区别。这两个是 Class 类型的相同实例。

你的警告意味着你在运行时没有通用参数中的信息,编译器也无法验证它,它只能信任你

我看不出将 KClass 转换为 KClass<MyInterface> 的原因。它只是一个对象所必需的,而不是class。此外,它可能可以简化为使用 Class<*> 代替,例如:

val className = "some.class.Name"
val unsafeClass = Class.forName(className)
require(MyInterface::class.java.isAssignableFrom(unsafeClass)) {
    "Class '$className' is not a MyInterface"
}
val safe = unsafeClass.newInstance() as MyInterface

这个转换不仅未经检查,而且实际上是不正确的:因为 AMyInterfaceImpl::class 的类型是 KClass<AMyInterfaceImpl>KClass 不是协变的(有充分的理由),它 not 具有类型 KClass<MyInterface>。您可以从这段代码中看到未编译:

class AMyInterfaceImpl : MyInterface { ... }

val cls: KClass<MyInterface> = AMyInterfaceImpl::class

因此,如果可以检查演员表,它就会失败。

KClass<out MyInterface> 是正确的,但我认为编译器不会理解这一点并允许智能转换。教编译器的用处太少了。