首页 > 解决方案 > 避免未经检查的演员表

问题描述

在以下情况下,我找不到避免未经检查的演员表的方法

class EventBus {
    val eventToHandle: MutableMap<KClass<out Event>, Event.() -> Unit> = mutableMapOf()

    final inline fun <reified T : Event> register(noinline handler: T.() -> Unit) {
        @Suppress("UNCHECKED_CAST")
        eventToHandle[T::class] = handler as Event.() -> Unit
    }

    fun fire(event: Event) {
        eventToHandle[event::class]?.invoke(event)
            ?: throw IllegalStateException("Missing handler for class ${event::class}")
    }
}

我的目标是强制映射仅包含具有Event(或其子类型之一)作为接收器的 lambda,但同时我希望在注册 lambda 时引用实际实现。Event通过这种方式,我可以使用他的实现成员,而无需每次都进行强制转换。

一个例子(requestId是 的一个字段RequestExpiredEvent):

eventBus.register<RequestExpiredEvent> {
            requestService.setExpiredByRequestId(requestId)
        }

我知道“消费者 lambda”没有协方差并且它们是逆变的,但我想是否有办法。我发现一个丑陋的解决方法是:

    final inline fun <reified T : Event> register(crossinline block: T.() -> Unit) {
        val handler: Event.() -> Unit = { this as T; block(this) }
        eventToHandle[T::class] = handler
    }

谢谢

标签: genericskotlincasting

解决方案


这是一个危险的演员阵容。SomeEventSubtype.() -> Unit不是 的子类型Event.() -> Unit。这是相反的方式。

假设 Event 是一个开放类并且你有这个子类:

class SubEvent: Event() {
    fun hello(): Unit {
        println("hello")
    }
}

现在你尝试投射它:

val hello: SubEvent.() -> Unit = SubEvent::hello
val helloCasted = hello as Event.() -> Unit

当您尝试调用helloCasted.invoke(Event())时,它会在尝试将您的事件转换为子事件时抛出 ClassCastException。您不能hello使用任何实例Event作为输入来调用,因为只有SubEvent一个hello函数可以调用。

如果您尝试隐式转换它,编译器将捕获此错误:

val hello: SubEvent.() -> Unit = SubEvent::hello
val helloCasted: Event.() -> Unit = hello // compiler error

它确实以相反的方式工作。在查看函数的输入时,您可以将类型层次结构视为倒置的。

val toStringFun: Event.() -> Unit = Event::toString
val toStringCasted: SubEvent.() -> Unit = toStringFun // OK

如果不在某处进行未经检查的强制转换,就没有办法解决这个问题,因为您在地图中存储了不同类型的对象。但是您需要将您的投射移动到该fire函数,以便它知道将其投射到什么。您可以将函数作为类型存储Any在地图中,因为无论如何您都会投射它们。像这样的东西:

class EventBus {
    val eventToHandle: MutableMap<KClass<out Event>, Any> = mutableMapOf()

    inline fun <reified T : Event> register(noinline handler: T.() -> Unit) {
        eventToHandle[T::class] = handler
    }

    @Suppress("UNCHECKED_CAST")
    inline fun <reified T : Event> fire(event: T) {
        (eventToHandle[T::class] as? T.() -> Unit)?.invoke(event)
            ?: throw IllegalStateException("Missing handler for class ${event::class}")
    }
}

推荐阅读