【问题标题】:Putting a generic lambda into a map将通用 lambda 放入地图
【发布时间】:2016-03-13 18:11:40
【问题描述】:

好的,下面的代码是一个执行以下操作的事件系统:

  1. 将整数 id 分配给 lambda 表达式
  2. 将 lambda 的 id 放入事件的可变集合中
  3. 将整数 ID 映射到 lambda 表达式
  4. 返回 id(以后可用于从 lambda 中删除事件)

代码如下:

class EventHandler {
    companion object {
        val handlers = HashMap<KClass<out Event>, MutableSet<Int>>()
        val idMap = HashMap<Int, (Event) -> Unit>();

        /**
         * @param event     Class of the event you are registering
         * @param handler   What to do when the event is called
         */
        fun <T : Event> register(event: KClass<T>, handler: (T) -> Unit): Int {
            var id: Int = 0;
            while(idMap[id] != null) {
                id++;
            }
            var list = handlers.getOrPut(event, {mutableSetOf()});
            list.add(id);
            idMap[id] = handler;
            return id;
        }
    }
}

这个方法的预期用途是这样的:

EventHandler.register(ChatEvent::class) { onChat ->
    println(onChat.message)
}

以下行有错误:idMap[id] = handler;

错误是因为处理程序的类型为(T) -&gt; Unit,尽管它需要为(Event) -&gt; Unit 才能将其添加到idMap。虽然我在创建时说过T 应该扩展Event,所以这应该不是问题。如果有解决方案,有谁知道为什么会发生这种情况?

【问题讨论】:

  • 有什么理由需要它是一个伴生对象而不是它自己的类?
  • @DougStevenson 因为我只打算有一个 EventHandler 实例。我尝试将其设为object 而不是class,但这并不能解决问题(如我所料)
  • Kotlin 有自己的单例处理方式:kotlinlang.org/docs/reference/object-declarations.html
  • @DougStevenson 我也试过了,没用
  • 它可以将任何事件作为参数,但它只能是基本类型 Event,而不是子类类型。就像常规方法一样,lambda 的参数类型必须在编译时绝对知道。它不能是模棱两可的或间接的。因此,如果这是您想要的,您必须将其转换为 lambda 以获得更具体的类型。

标签: generics lambda kotlin


【解决方案1】:

问题在于idMap 采用一个接收Event 的函数 - 任何Event。 然后,您尝试注册一个函数,该函数采用Event 的指定子类,当您将其拉回时,编译器将无法判断它接收到的可能子类。是的,您将特定类型存储在其他映射中,但编译器不能使用它。

我不相信您可以创建您想要创建的映射系统。不是没有更多的间接或抽象层......

【讨论】:

    【解决方案2】:

    @jacob-zimmerman 在他的answer 中很好地解释了您收到此错误的原因:(T) -&gt; Unit 不是(Event) -&gt; Unit 的子类型(相反它是超类型)。

    您可以对所需的函数类型进行未经检查的向下转换:

    idMap[id] = handler as (Event) -> Unit
    

    但是在调用此类处理程序之前,您必须检查事件是否具有处理程序可以接受的类型,例如通过根据事件类型从映射查询处理程序:

    fun invoke(event: Event) {
        val kclass = event.javaClass.kotlin
        val eventHandlers = handlers[kclass]?.map { idMap[it]!! } ?: return
        eventHandlers.forEach { it.invoke(event) }
    }
    

    【讨论】:

      【解决方案3】:

      我想出了一个实现:

      class Handlers<T: Event> {
          val backingList = ArrayList<(T) -> Unit>()
      
          fun add(handler: (T) -> Unit) {
              backingList.add(handler)
          }
      
          fun remove(handler: (T) -> Unit) {
               backingList.remove(handler)
          }
      
          fun handleEvent(event: T) {
              backingList.forEach { handle ->
                  handle(event)
              }
          }
      }
      
      class HandlerMap {
           val handlerMap = HashMap<KClass<out Event>, Handlers<out Event>>()
      
          fun <T : Event> register(event: KClass<T>, handler: (T) -> Unit) {
              val list: Handlers<T>
              if(!handlerMap.containsKey(event)) {
                  list = Handlers<T>()
                  handlerMap.put(event, list)
              }
              else {
                  list = handlerMap.get(event) as Handlers<T>
              }
      
              list.add(handler)        
          }
      
          fun <T: Event> getHandlers(event: KClass<T>): Handlers<T> {
              return handlerMap.get(event) as Handlers<T>
          }
      }
      

      由于 Map 有点开放,它必须进行一些转换,但系统是关闭的,所以 Handlers 对象保证是完全正确的类型。 由于实现的变化,我很确定你不再关心索引/“id”,但我尝试了一个实现,它很好;如果你想要的话,把它放进去并不麻烦。

      【讨论】:

        猜你喜欢
        • 2018-01-15
        • 1970-01-01
        • 1970-01-01
        • 2022-01-09
        • 1970-01-01
        • 2012-10-20
        • 1970-01-01
        • 2017-01-19
        相关资源
        最近更新 更多