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

时间:2015-04-08 01:35:48

标签: java events generics

此问题与How to create and fire a collection of Consumers<T> in java for an event listener pattern有关。我试图使用一系列事件来帮助批量操作,但到目前为止,我似乎无法提供正确的通用定义配置。

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

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);之外,没有任何投射可以解决这个问题,但这对我来说看起来很恐怖,似乎我必须遗漏某些东西,或者弄乱一些东西,因为它没有&#39 ;似乎这应该是必需的步骤。

2 个答案:

答案 0 :(得分:1)

不,这几乎是如何运作的。 Java的类型系统无法表达您试图表达的约束。您的包装器实现是类型安全的,但编译器无法证明它,因此您必须进行脏的不安全转换以说服编译器您知道您正在做什么。

答案 1 :(得分:1)

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

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

值的类型实际上应该是

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

不是

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

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

最后,eventToFire.getClass()不是您想要的,因为它会返回某种集合类型的类,而您的键的类型为Class<? extends Event>。由于类型擦除,您无法在运行时从集合中获取type参数,因此您还需要将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));
    }
}

所需的唯一强制转换是在两行中假设键和值匹配,但这是你可以做的最好的。