我总是很难将泛型与集合和通配符一起使用。
所以这是以下地图。我想保留特定类型的数据包类的处理程序集合。
private ConcurrentHashMap<Class<? extends Packet>, List<PacketListener<? extends Packet>>> listeners = new ConcurrentHashMap<>();
PacketListener
public interface PacketListener<T extends Packet> {
public void onOutgoingPacket(Streamer streamer, T packet);
public void onIncomingPacket(Streamer streamer, T packet);
}
现在我想做的是根据传入的数据包类来获取侦听器:
public <T extends Packet> void addPacketListener(Class<T> clazz, PacketListener<T> listener) {
if (listeners.containsKey(clazz) == false) {
listeners.putIfAbsent(clazz, new LinkedList<PacketListener<T>>()); // ERROR
}
List<PacketListener<? extends Packet>> list = listeners.get(clazz);
list.add(listener);
}
public <T extends Packet> List<PacketListener<T>> getPacketListeners(Class<T> clazz) {
List<PacketListener<T>> list = listeners.get(clazz);// ERROR
if (list == null || list.isEmpty()) {
return null;
} else {
return new ArrayList<>(list);
}
}
最后我想执行这样的调用
private <T extends Packet> void notifyListeners(T packet) {
List<PacketListener<T>> listeners = streamer.getPacketListeners(packet.getClass());
if (listeners != null) {
for (PacketListener<? extends Packet> packetListener : listeners) {
packetListener.onIncomingPacket(streamer, packet);
}
}
}
我所得到的只是很多错误。是因为收集声明中的通配符吗?是否有可能实现这样的解决方案?
答案 0 :(得分:5)
有一个很好的形象: 其中一个answers可以解释你这个问题。
这个东西叫PECS,代表
制作人
extends
和消费者super
。
TL; DR:您只能{/ 1}}和add
来自/来自具体类型(get
)的集合。您可以使用T
获取任何T
(及其可能的子类型),并且可以T extends Something
添加任何Something
Collection
但您不能去两种方式:因此你的错误。
答案 1 :(得分:1)
您的问题从此处开始:
private ConcurrentHashMap<Class<? extends Packet>, List<PacketListener<? extends Packet>>> listeners = new ConcurrentHashMap<>();
您期望(或者只是希望)将两个?
绑定在一起的方法,以便使用类型Class<T>
的键进行查找将得到类型{{1}的值}。遗憾的是,没有办法告诉Java两个List<PacketListener<T>>
是相同的但可以采用不同(但受约束)的类型。
此问题通常使用其他地方提到的?
方法解决,但在您的情况下,您需要同时从您的收藏集中读取和。因此,必须使用covariance/contravariance
。
我相信你的问题的解决方案是将两个对象绑定到一个辅助类中,从而在那里引入不变性。这样你就可以保持他们的平等,同时让他们在限制下变化。
其中一些是有点hacky恕我直言(即有一些演员),但至少你可以实现你的目标,你仍然是类型安全。演员证明是有效的。
invariance
请注意,虽然通常假设如果您在使用泛型时需要进行某些操作,但是您做错了 - 在这种情况下,我们可以安全,因为运行时保证所有{ {1}}地图中的对象由其public interface PacketListener<T extends Packet> {
public void onOutgoingPacket(Streamer streamer, T packet);
public void onIncomingPacket(Streamer streamer, T packet);
}
/**
* Binds the T's of Class<T> and PacketListener<T> so that we CAN assume they are the same type.
*
* @param <T> The type of Packet we listen to.
*/
private static class Listeners<T extends Packet> {
final Class<T> packetClass;
final List<PacketListener<T>> listenerList = new LinkedList<>();
public Listeners(Class<T> packetClass) {
this.packetClass = packetClass;
}
public List<PacketListener<T>> getListenerList() {
return listenerList;
}
private void addListener(PacketListener<T> listener) {
listenerList.add(listener);
}
}
/**
* Now we have bound the T of Class<T> and List<PacketListener<T>> by using the Listeners class we do not need to key on the Class<T>, we just need to key on Class<?>.
*/
private final ConcurrentMap<Class<?>, Listeners<?>> allListeners = new ConcurrentHashMap<>();
public <T extends Packet> List<PacketListener<T>> getPacketListeners(Class<T> clazz) {
// Now we can confidently cast it.
Listeners<T> listeners = (Listeners<T>) allListeners.get(clazz);
if (listeners != null) {
// Return a copy of the list so they cannot change it.
return new ArrayList<>(listeners.getListenerList());
} else {
return Collections.EMPTY_LIST;
}
}
public <T extends Packet> void addPacketListener(Class<T> clazz, PacketListener<T> listener) {
// Now we can confidently cast it.
Listeners<T> listeners = (Listeners<T>) allListeners.get(clazz);
if (listeners == null) {
// Make one.
Listeners<T> newListeners = new Listeners<>();
if ((listeners = (Listeners<T>) allListeners.putIfAbsent(clazz, newListeners)) == null) {
// It was added - use that one.
listeners = newListeners;
}
}
// Add the listener.
listeners.addListener(listener);
}
键控,因此所附的列表确实是Listeners<T>
。
答案 2 :(得分:0)
以下是@OldCurmudgeon的回答。
关键点也是listeners
字段。但我声明如下:
private final Map<Class<?>, DelegatingPacketListener> listeners
这里的要点是我们将列表删除为地图值类型。 DelegatingPacketListener
声明如下:
public class DelegatingPacketListener implements PacketListener<Packet> {
private final List<PacketListener<Packet>> packetListeners;
public DelegatingPacketListener(List<? extends PacketListener<Packet>> packetListeners) {
super();
this.packetListeners = new ArrayList<PacketListener<Packet>>(packetListeners);
}
@Override
public void onOutgoingPacket(Streamer streamer, Packet packet) {
for(PacketListener<Packet> packetListener : packetListeners) {
packetListener.onOutgoingPacket(streamer, packet);
}
}
@Override
public void onIncomingPacket(Streamer streamer, Packet packet) {
for(PacketListener<Packet> packetListener : packetListeners) {
packetListener.onIncomingPacket(streamer, packet);
}
}
public List<PacketListener<Packet>> getPacketListeners() {
return Collections.unmodifiableList(packetListeners);
}
}
既然DelegatingPacketListener
仅支持Packet
类型的侦听器,我们还需要一个PacketListener
的具体实现:
public class WrappingPacketListener<T extends Packet> implements PacketListener<Packet> {
private final Class<T> packetClass;
private final PacketListener<T> wrapped;
public WrappingPacketListener(Class<T> packetClass, PacketListener<T> delegate) {
super();
this.packetClass = packetClass;
this.wrapped = delegate;
}
@Override
public void onOutgoingPacket(Streamer streamer, Packet packet) {
if(packetClass.isInstance(packet)) {
T genericPacket = packetClass.cast(packet);
wrapped.onOutgoingPacket(streamer, genericPacket);
}
}
@Override
public void onIncomingPacket(Streamer streamer, Packet packet) {
if(packetClass.isInstance(packet)) {
T genericPacket = packetClass.cast(packet);
wrapped.onIncomingPacket(streamer, genericPacket);
}
}
}
请注意,implements子句中未使用类型参数T
。它仅适用于所使用的实现。我们将在PacketListener
中将传递给API的每个WrappingPacketListener
换行。所以实现是这样的:
public List<PacketListener<Packet>> getPacketListeners(Class<?> clazz) {
return Collections.<PacketListener<Packet>>singletonList(listeners.get(clazz));
}
public <T extends Packet> void addPacketListener(Class<T> clazz, PacketListener<T> listener) {
if (listeners.containsKey(clazz) == false) {
listeners.put(clazz, new DelegatingPacketListener(Collections.singletonList(new WrappingPacketListener<T>(clazz, listener))));
return;
}
DelegatingPacketListener existing = listeners.get(clazz);
List<PacketListener<Packet>> newListeners = new ArrayList<PacketListener<Packet>>(existing.getPacketListeners());
newListeners.add(new WrappingPacketListener<T>(clazz, listener));
listeners.put(clazz, new DelegatingPacketListener(newListeners));
}
private <T extends Packet> void notifyListeners(T packet) {
List<PacketListener<Packet>> listeners = streamer.getPacketListeners(packet.getClass());
if (listeners != null) {
for (PacketListener<Packet> packetListener : listeners) {
packetListener.onIncomingPacket(streamer, packet);
}
}
}
getPacketListeners
的API略有变化,不再使用泛型类型。
与OldCurmudgeon的解决方案相比,这个解决方案坚持使用现有的PacketListener
界面,并且不需要应用未经检查的强制转换。
请注意,实现不是线程安全的,因为addPacketListener
的实现需要在map键上进行同步(因为有问题的原始代码也需要)。但是,将数据包侦听器列表封装在不可变DelegatingPacketListener
中可能更适合并发目的。