如何声明泛型处理程序的通用多字形集合

时间:2015-05-22 08:32:37

标签: java generics

我总是很难将泛型与集合和通配符一起使用。

所以这是以下地图。我想保留特定类型的数据包类的处理程序集合。

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);
        }
    }
}

我所得到的只是很多错误。是因为收集声明中的通配符吗?是否有可能实现这样的解决方案?

3 个答案:

答案 0 :(得分:5)

有一个很好的形象: PECS 其中一个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中可能更适合并发目的。