简体   繁体   English

如何声明泛型处理程序的泛型multitype集合

[英]How to declare generic multitype collection of generic handlers

I always have hard time using generics with collections and wildcards. 我总是很难将泛型与集合和通配符一起使用。

So here is the following map. 所以这是以下地图。 I want to keep collection of handlers for a specific type of packet class. 我想保留特定类型的数据包类的处理程序集合。

private ConcurrentHashMap<Class<? extends Packet>, List<PacketListener<? extends Packet>>> listeners = new ConcurrentHashMap<>();

And the PacketListener PacketListener

public interface PacketListener<T extends Packet> {

    public void onOutgoingPacket(Streamer streamer, T packet);

    public void onIncomingPacket(Streamer streamer, T packet);
}

now what I would like to do is to get listeners depending on incoming packet class like this: 现在我想做的是根据传入的数据包类获取侦听器,如下所示:

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

And finally I would like to perform such invocation 最后我想执行这样的调用

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

All I am getting are just lot of errors. 我所得到的只是很多错误。 Is it because of wildcards in collection declaration? 是因为收集声明中的通配符吗? Is it possible to achieve such solution? 有可能实现这样的解决方案吗?

There is a nice image: 有一个很好的形象: 佩奇 In one of the other answers which can explain you this problem. 在其中一个可以解释你这个问题的答案中。

The thing is called PECS which stands for 这个东西叫PECS,代表

Producer extends and Consumer super . 制作人extends和消费者super

TL;DR: you can only both add and get from/to a collection with a concrete type ( T ). TL; DR:您只能addget具有特定类型( T )的集合。 You can get any T (and its possible subtypes) with T extends Something and you can add any Something to a Collection with T super Something but you can't go both ways: thus your errors. 你可以使用T extends Something获得任何T (及其可能的子类型),你可以使用T super Something将任何Something添加到Collection ,但是你不能双向:因此你的错误。

Your issue starts here: 你的问题从这里开始:

private ConcurrentHashMap<Class<? extends Packet>, List<PacketListener<? extends Packet>>> listeners = new ConcurrentHashMap<>();

You are expecting (or perhaps just hoping) for a way to bind the two ? 你期望(或者只是希望)一种绑定两者的方法? together so that a lookup with a key of type Class<T> will result in a value of type List<PacketListener<T>> . 一起使用类型为Class<T>的键进行查找将得到List<PacketListener<T>>类型的值。 Sadly there is no way to tell Java that the two ? 可悲的是,没有办法告诉Java这两个? are the same but can take different (but constrained) types. 是相同的,但可以采取不同(但受约束)的类型。

This issue is usually solved using the covariance/contravariance methods mentioned elsewhere but in your case you need to both write and read from your collection. 这个问题通常使用其他地方提到的covariance/contravariance方法来解决,但在您的情况下,您需要同时写入读取您的收藏。 You therefore must use an invariance . 因此,您必须使用invariance

I believe a solution to your problem is to bind the two objects into one helper class and therefore introduce the invariance there. 我相信你的问题的解决方案是将两个对象绑定到一个辅助类中,从而在那里引入不变性。 This way you can maintain their equality while still letting them vary under restrictions. 这样你就可以保持他们的平等,同时让他们在限制下变化。

Some of this is a little hacky IMHO (ie there are some casts) but at least you can achieve your aim and you are still type safe. 其中一些是一些hacky恕我直言(即有一些演员),但至少你可以实现你的目标,你仍然是类型安全。 The casts are provably valid. 演员证明是有效的。

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

Note that although it is generally assumed that if you need to cast something while using generics you are doing something wrong - in this case we can be safe because of the run-time assurance that all Listeners<T> objects in the map are keyed by their Class<T> and therefore the enclosed list is indeed a List<PacketListener<T> . 请注意,尽管通常假设如果您在使用泛型时需要进行某些操作,但是您做错了 - 在这种情况下,我们可以安全,因为运行时保证地图中的所有Listeners<T>对象都被键入他们的Class<T>因此附带的列表确实是List<PacketListener<T>

The following is some kind of similiar to the answer of @OldCurmudgeon. 以下是@OldCurmudgeon的回答类似。

The keypoint is also the listeners field. 关键点也是listeners领域。 But I declare it as this: 但我声明如下:

private final Map<Class<?>, DelegatingPacketListener> listeners

The point here is that we get rid of the list as the map value type. 这里的要点是我们将列表删除为地图值类型。 DelegatingPacketListener is declared as follows: 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);
    }
}

Now that DelegatingPacketListener only supports listeners of type Packet we need one more specific implementation of PacketListener : 现在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);
        }
    }
}

Please note that the type parameter T is not used in the implements clause. 请注意,implements子句中未使用类型参数T It is only for the implementation used. 它仅适用于所使用的实现。 We will wrap every PacketListener passed to the API in a WrappingPacketListener . 我们将在WrappingPacketListener传递给API的每个PacketListener So the implementation is like this: 所以实现是这样的:

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

The API has slightly changed for getPacketListeners which doesnt use a generic type anymore. 对于getPacketListeners使用泛型类型的getPacketListeners ,API略有变化。

In comparison with OldCurmudgeon's solution, this one sticks with the already existing PacketListener interface and doesn't require an unchecked cast to be applied. 与OldCurmudgeon的解决方案相比,这个解决方案坚持使用现有的PacketListener接口,并且不需要应用未经检查的强制转换。

Note that the implementation is not thread safe, because of the implemention of addPacketListener needs synchronization on the map key (as the original code in question does need too). 请注意,实现不是线程安全的,因为addPacketListener的实现需要在map键上进行同步(因为有问题的原始代码也需要)。 However encapsulating the list of packet listeners in immutable DelegatingPacketListener is probably better suited for concurrency purposes. 但是,在不可变的DelegatingPacketListener封装数据包侦听器列表可能更适合并发目的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM