【问题标题】:How to safely cast a reflected class in Kotlin如何在 Kotlin 中安全地投射反射类
【发布时间】:2019-02-24 12:34:37
【问题描述】:

我需要在运行时在 Kotlin 中动态加载类。我想检查他们是否实现了我的界面,如果是这样,全是绿色的。不幸的是,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&lt;MyInterface&gt; 进行测试,但出现类型擦除错误(很明显,因为泛型类型信息在运行时消失了。)


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

我的问题:有什么方法可以做到不收到不安全的演员表警告?

当我将KClass&lt;*&gt; 转换为KClass&lt;MyInterface&gt; 时,我可以在配置时收到警告(即使我将required 类作为子类),但后来我没有收到任何警告,因为@ KClass&lt;MyInterface&gt; 类引用上的 987654328@ 返回经过类型检查的 MyInterface 实例。

或者,我可以将引用存储为KClass&lt;*&gt;,而不会在配置时发出警告,但是我会在创建实例的地方收到警告,因为我需要不安全地强制转换Object 实例到MyInterface

有没有可以让编译器满意的解决方案?

【问题讨论】:

    标签: reflection kotlin casting type-erasure


    【解决方案1】:

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

    在运行时,Class&lt;*&gt;Class&lt;MyInterface&gt; 之间没有区别。这两个是Class 类型的同一个实例。

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

    我看不出将KClass 转换为KClass&lt;MyInterface&gt; 的原因。它只对一个对象是必需的,而不是它的类。此外,它可能可以简化为使用Class&lt;*&gt;,例如:

    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
    

    【讨论】:

    • 谢谢。我扩大了我的问题。将KClass&lt;*&gt; 转换为KClass&lt;MyInterface&gt; 的原因是为了避免必须转换我稍后将创建的所有对象。
    • 对于演员表,它等于使用 KClass 或 Class。您有来自Class.forName 电话的Class&lt;?&gt;。无论如何,您都需要显式强制转换或不安全强制转换
    【解决方案2】:

    这种转换不仅未经检查,而且实际上是不正确的:因为AMyInterfaceImpl::class 的类型为KClass&lt;AMyInterfaceImpl&gt;,而KClass 不是协变的(有充分的理由),它确实 有@ 类型987654325@。您可以从这段代码中看到没有编译:

    class AMyInterfaceImpl : MyInterface { ... }
    
    val cls: KClass<MyInterface> = AMyInterfaceImpl::class
    

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

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

    【讨论】:

    • 谢谢。我扩大了我的问题。那么,如果我希望能够创建满足MyInterface 的对象实例,我应该存储什么Class&lt;...&gt;KClass&lt;...&gt; 引用?我应该使用KClass&lt;*&gt;,并在每个对象创建时进行不安全的强制转换吗?或者进行不安全的强制转换以获得KClass&lt;out MyInterface&gt;,这意味着我不需要强制转换来创建实例?我可以摆脱这两个警告,还是不可能?
    • 如果使用KClass&lt;*&gt;,为什么会收到警告? Cast to MyInterface 被选中,您甚至可以测试 is MyInterface 以避免显式转换,但我认为那里没有太大的胜利。
    • 您可以使用@Suppress("UNCHECKED_CAST") stackoverflow.com/questions/36221405/… 来抑制警告。在运行时,这两种方式实际上完全相同:不存在转换为KClass&lt;out MyInterface&gt;,编译器在需要的地方插入转换为MyInterface。所以使用更具可读性的那个。
    猜你喜欢
    • 2023-02-03
    • 2013-01-16
    • 1970-01-01
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多