将通用lambda放入地图中

时间:2016-03-13 18:11:41

标签: generics lambda kotlin

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

  1. 为lambda表达式
  2. 指定一个整数id
  3. 将lambda的id放入事件的可变集
  4. 将整数ID映射到lambda表达式
  5. 返回id(稍后可用于从lambda中删除事件)
  6. 代码如下:

    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) -> Unit类型,但需要(Event) -> Unit才能将其添加到idMap。虽然我说T在我创建Event时会延长f n = n : f (n+1) ,所以这不应该是个问题。如果有解决方案,有谁知道为什么会发生这种情况?

3 个答案:

答案 0 :(得分:4)

问题来自idMap采用接收Event - 任何Event的函数的事实。 然后你尝试注册一个带有Event的指定子类的函数,当你把它拉出来时,编译器就不能告诉它接收到什么可能的子类。是的,您在其他地图中存储了特定类型,但编译器无法使用该类型。

我不相信你可以创建你想要创建的地图系统。不是没有几层间接或抽象......

答案 1 :(得分:3)

@ jacob-zimmerman在answer中解释了出现此错误的原因:(T) -> Unit不是(Event) -> Unit的子类型(相反,它是超类型)。

您可以将未经检查的向下转换为所需的函数类型:

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

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

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

答案 2 :(得分:1)

我想出了一个实现:

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对象保证是完全正确的类型。 由于实施的变化,我很确定你并不关心索引/&#34; id&#34;我已经尝试了它的实现,它很好;如果你想要的话,没有真正的麻烦。