2015-05-22 13 views
8

Ho sempre difficoltà a utilizzare i generici con raccolte e caratteri jolly.Come dichiarare una raccolta multitaspe generica di gestori generici

Quindi, ecco la seguente mappa. Voglio mantenere la raccolta di gestori per un tipo specifico di classe di pacchetti.

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

E il PacketListener

public interface PacketListener<T extends Packet> { 

    public void onOutgoingPacket(Streamer streamer, T packet); 

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

ora quello che vorrei fare è quello di ottenere gli ascoltatori a seconda della classe di pacchetto in arrivo in questo modo:

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

E infine vorrei svolgere tale invocazione

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

Tutto quello che sto ottenendo sono solo molti errori. È a causa di caratteri jolly nella dichiarazione di raccolta? È possibile raggiungere tale soluzione?

+0

Provare a seguire il principio PECS. Renderà le tue cose facili. http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs – Pranalee

+0

Si prega di notare che anche se si utilizza un 'ConcurrentMap' il codice in' addPacketListener' non è thread-safe e richiederebbe la sincronizzazione sulla mappa chiave. – SpaceTrucker

+0

@SpaceTrucker perché in realtà? – Antoniossss

risposta

4

C'è una bella immagine: PECS In uno degli altri answers che può spiegare questo problema.

La cosa è chiamato PECS che sta per

Producer e Consumer extendssuper.

TL; DR: si può solo sia add e get da/a una collezione con un tipo concreto (T). Puoi ottenere qualsiasi T (e i suoi possibili sottotipi) con T extends Something e puoi aggiungere qualsiasi Something a Collection con ma non puoi andare in entrambi i modi: quindi i tuoi errori.

+1

Questo è fantastico! – Ian2thedv

+0

Nota che l'immagine non è stata disegnata da me: controlla il link che ho provato per una risposta più dettagliata. –

1

vostro problema comincia qui:

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

È aspettate (o forse solo sperando) di un modo per legare le due ? insieme in modo che una ricerca con una chiave di tipo Class<T> si tradurrà in un valore di digitare List<PacketListener<T>>. Purtroppo non c'è modo di dire a Java che i due ? sono uguali ma possono assumere tipi diversi (ma limitati).

Questo problema viene generalmente risolto utilizzando i metodi citati covariance/contravariance altrove, ma nel tuo caso è necessario sia per scrivere e leggere dalla tua collezione. Pertanto, è necessario utilizzare un invariance.

Credo che una soluzione al tuo problema sia di legare i due oggetti in una classe di supporto e quindi introdurre l'invarianza lì. In questo modo puoi mantenere la loro uguaglianza pur lasciandoli variare sotto restrizioni.

Alcuni di questi sono un piccolo IMHO hacky (cioè ci sono alcuni gessi) ma almeno puoi raggiungere il tuo obiettivo e sei ancora sicuro. I lanci sono provabilmente validi.

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

Nota che, anche se è generalmente accettato che se avete bisogno di esprimere qualcosa durante l'utilizzo di farmaci generici che si sta facendo qualcosa di sbagliato - in questo caso siamo in grado di essere al sicuro a causa della runtime garanzia che tutti Listeners<T> oggetti nella mappa sono codificati dal loro Class<T> e quindi l'elenco allegato è in effetti un List<PacketListener<T>.

0

Quanto segue è una sorta di simile alla risposta di @OldCurmudgeon.

Il punto chiave è anche il campo listeners. Ma dichiaro come questo:

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

Il punto qui è che ci liberiamo della lista, come il tipo di mappa di valore. DelegatingPacketListener è dichiarato come segue:

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

Ora che DelegatingPacketListener supporta solo gli ascoltatori di tipo Packet abbiamo bisogno di un'altra specifica implementazione di 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); 
     } 
    } 
} 

prega di notare che il parametro di tipo T non viene utilizzato nel clausola sugli strumenti. È solo per l'implementazione utilizzata. Copieremo ogni PacketListener passato all'API in un WrappingPacketListener. Quindi l'implementazione è in questo modo:

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

L'API è leggermente cambiata per getPacketListeners che pretende molto più usare un tipo generico.

Rispetto alla soluzione di OldCurmudgeon, questo si integra con l'interfaccia PacketListener già esistente e non richiede l'applicazione di un cast non controllato.

Si noti che l'implementazione non è thread-safe, poiché l'implementazione di addPacketListener richiede la sincronizzazione sulla chiave della mappa (poiché anche il codice originale in questione ne ha bisogno). Tuttavia, incapsulare l'elenco dei listener di pacchetti in immutable DelegatingPacketListener è probabilmente più adatto per scopi di concorrenza.

Problemi correlati