如何在带有通配符的通用映射中使用带有类型化参数的通用集合 (Java)

How to use generic collections with typed parameters in a generic map with wildcards (Java)

此问题与 有关。我试图使用事件集合来帮助批处理操作,但到目前为止我似乎无法想出通用定义的正确配置。

到目前为止,这是我的代码。

private ConcurrentHashMap<Class<? extends Event>, ConcurrentLinkedQueue<Consumer<Collection<? extends Event>>>> listeners;

public <T extends Event> void listen(Class<T> clazz, Consumer<Collection<T>> consumer){
    ConcurrentLinkedQueue<Consumer<Collection<T>>> consumers = listeners.get(clazz);
    if (consumers == null) {
        consumers = new ConcurrentLinkedQueue<>();
        listeners.put(clazz, consumers); // Complains that consumers is not the type Collection<? extends Event>
    }
    consumers.add(consumer);
}

public <T extends Event> void fire(Collection<T> eventToFire){
    ConcurrentLinkedQueue<Consumer<Collection<? extends Event>>> consumers = listeners.get(eventToFire.getClass());
    if(consumers != null){
        consumers.forEach(x -> x.accept(eventToFire));
    }
}

我试过将 ConcurrentLinkedQueue<Consumer<Collection<T>>> consumers = listeners.get(clazz); 设置为 ConcurrentLinkedQueue<Consumer<Collection<? extends Event>>> consumers = listeners.get(clazz); 但随后它抱怨 consumers.add(consumer); 不是 Consumer<Collection<T>> 类型。

除了 consumers.add((Consumer<Collection<? extends Event>>)(Object)consumer); 之外,似乎没有演员可以解决这个问题,但这对我来说太可怕了,看来我一定是遗漏了什么,或者搞砸了什么,因为它没有' 看起来这应该是必需的步骤。

不,这几乎就是它的工作原理。 Java 的类型系统无法表达您要表达的约束。您的包装器实现是类型安全的,但编译器无法证明这一点,因此您必须进行肮脏的不安全转换才能说服编译器您知道自己在做什么。

您希望编译器知道如果键的类型为 Class<T>(其中 T extends Event),则值为 ConcurrentLinkedQueue<Consumer<Collection<T>>>

不幸的是,没有办法表达这个事实。但是,您知道这是真的,因为您放入地图的唯一 key/value 对具有这种形式。因此,放入演员阵容是安全的,但我还会附上一条评论,解释为什么可以。

值的类型实际上应该是

ConcurrentLinkedQueue<? extends Consumer<? extends Collection<? extends Event>>>

不是

ConcurrentLinkedQueue<Consumer<Collection<? extends Event>>>

后者是一个Consumers的队列,它必须能够接受任何类型扩展Event的集合,但你只想要一个Consumer 接受 某些特定类型 TCollections 扩展 Event。理解这一点难以置信,但请记住,当您将通配符与嵌套类型参数混合使用时,通常在每个级别都需要 ? extends

最后,eventToFire.getClass() 不是您想要的,因为这个 returns 是某种集合类型的 class,而您的键是 Class<? extends Event> 类型。由于类型擦除,您无法在运行时从集合中获取类型参数,因此您还需要显式地将 clazz 传递给 fire

经过这些更改,代码变为

private ConcurrentHashMap<Class<? extends Event>, ConcurrentLinkedQueue<? extends Consumer<? extends Collection<? extends Event>>>> listeners;

public <T extends Event> void listen(Class<T> clazz, Consumer<Collection<T>> consumer){
    ConcurrentLinkedQueue<Consumer<Collection<T>>> consumers = (ConcurrentLinkedQueue<Consumer<Collection<T>>>) listeners.get(clazz);
    if (consumers == null) {
        consumers = new ConcurrentLinkedQueue<>();
        listeners.put(clazz, consumers);
    }
    consumers.add(consumer);
}

public <T extends Event> void fire(Class<T> clazz, Collection<T> eventToFire){
    ConcurrentLinkedQueue<Consumer<Collection<T>>> consumers = (ConcurrentLinkedQueue<Consumer<Collection<T>>>) listeners.get(clazz);
    if(consumers != null){
        consumers.forEach(x -> x.accept(eventToFire));
    }
}

唯一需要的转换是在假设键和值匹配的两行中,但这是您能做的最好的。