如何在Kotlin中安全地投射反思性课程

时间:2019-02-23 17:01:06

标签: reflection kotlin casting type-erasure

我需要在Kotlin的运行时动态加载类。我想检查一下它们是否实现了我的界面,如果是,则全部变为绿色。不幸的是,科特林的“智能演员”让我失望了:

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>

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

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


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

我的问题:有没有办法得到不安全的转换警告?

当我将KClass<*>强制转换为KClass<MyInterface>时,我可以在配置时得到警告(即使我require将该类设为子类),但随后却没有稍后会发出警告,因为.createInstance()类引用上的KClass<MyInterface>返回类型检查过的MyInterface实例。

或者,我可以在没有配置警告的情况下将引用存储为KClass<*>,但是在创建实例的地方我会得到警告,因为我需要不安全地投射{{ 1}}个实例到Object

有没有可以满足编译器要求的解决方案?

2 个答案:

答案 0 :(得分:2)

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

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

您所拥有的警告意味着在运行时通用参数中没有信息,编译器也无法对其进行验证,并且它只能信任您

我看不到将KClass强制转换为KClass<MyInterface>的原因。它仅对于对象是必需的,而不是类。另外,使用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

答案 1 :(得分:1)

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

class AMyInterfaceImpl : MyInterface { ... }

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

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

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