此问题与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 ;似乎这应该是必需的步骤。
答案 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));
}
}
所需的唯一强制转换是在两行中假设键和值匹配,但这是你可以做的最好的。