2015-05-22 13 views
8

Zawsze mam problem z używaniem generycznych obiektów z kolekcjami i symbolami wieloznacznymi.Jak zadeklarować ogólny zbiór wielościeżkowy generycznych procedur obsługi

Oto następująca mapa. Chcę zachować kolekcję programów obsługi dla określonego typu klasy pakietów.

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

A PacketListener

public interface PacketListener<T extends Packet> { 

    public void onOutgoingPacket(Streamer streamer, T packet); 

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

teraz co chciałbym zrobić, to zdobyć słuchaczy w zależności od przychodzących klasy pakietowej tak:

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

I wreszcie chciałbym wykonać taka inwokacja

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

Wszystko, co dostaję, to po prostu wiele błędów. Czy to z powodu symboli wieloznacznych w deklaracji odbioru? Czy można osiągnąć takie rozwiązanie?

+0

Staraj się przestrzegać zasady PECS. Sprawi, że twoje rzeczy będą łatwe. http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs – Pranalee

+0

Nie należy dodawać, że mimo użycia 'ConcurrentMap' kod w' addPacketListener' nie jest bezpieczny dla wątków i wymagałby synchronizacji na mapie klawisz. – SpaceTrucker

+0

@SpaceTrucker, dlaczego tak się dzieje? – Antoniossss

Odpowiedz

4

Jest ładny obraz: PECS W jednym z pozostałych answers które mogą wyjaśnić Ci ten problem.

Rzecz nazywa PECS co oznacza

Producent extends i Konsumentów super.

TL; DR: można jedynie zarówno add i get z/do kolekcji z rodzaju betonu (T). Możesz uzyskać dowolny T (i jego możliwe podtypy) za pomocą T extends Something i możesz dodać dowolną Something do Collection z , ale nie możesz przejść w obie strony: w ten sposób Twoje błędy.

+1

To jest niesamowite! – Ian2thedv

+0

Zwróć uwagę, że obraz nie został narysowany przeze mnie: sprawdź link, który udowodniłem, aby uzyskać bardziej szczegółową odpowiedź. –

1

Twój problem zaczyna się tutaj:

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

Ty oczekujesz (a może tylko nadzieję) sposobu, aby powiązać te dwa ? razem tak, że wyszukiwanie z kluczem typu Class<T> spowoduje wartości typ List<PacketListener<T>>. Niestety nie ma sposobu, aby powiedzieć Java, że ​​dwa ? są takie same, ale mogą przyjmować różne (ale ograniczone) typy.

Ten problem jest zazwyczaj rozwiązywany za pomocą metod wymienionych w innym miejscu, ale w twoim przypadku musisz zarówno napisać: i odczytać z swojej kolekcji. Dlatego musisz musi użyć invariance.

Uważam, że rozwiązaniem twojego problemu jest powiązanie obu obiektów w jedną klasę pomocniczą, a zatem wprowadzenie tam niezmienności. W ten sposób możesz zachować równość, a jednocześnie pozwolić im na różne ograniczenia.

Niektóre z nich to trochę bezczelne IMHO (to jest niektóre odlewy), ale przynajmniej możesz osiągnąć swój cel i nadal jesteś bezpieczny. Rzuty są udowodnione.

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

Należy zauważyć, że chociaż na ogół zakłada się, że jeśli trzeba rzucić coś podczas korzystania rodzajowych robisz coś złego - w tym przypadku możemy być bezpieczny ze względu na run-time pewność, że wszystkie Listeners<T> obiekty na mapie są oznaczone kluczem Class<T> i dlatego załączona lista jest rzeczywiście List<PacketListener<T>.

0

Poniżej znajduje się coś w rodzaju podobnego do odpowiedzi @OldCurmudgeon.

Punktem kluczowym jest również pole listeners. Ale deklaruję to tak:

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

Chodzi o to, że pozbywamy się listy jako typu wartości mapy. DelegatingPacketListener jest zadeklarowana następująco:

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

Teraz DelegatingPacketListener obsługuje tylko słuchaczy typu Packet musimy jeszcze jedną konkretną realizację 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); 
     } 
    } 
} 

Zwróć uwagę, że parametr typu T nie jest stosowany w implementuje klauzulę. Dotyczy tylko zastosowanej implementacji. Owińemy każdy PacketListener przekazany do API w WrappingPacketListener. Zatem realizacja jest tak:

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

API nieznacznie zmieniło getPacketListeners który nie robi używać typu rodzajowego więcej.

W porównaniu do rozwiązania OldCurmudgeon, ten jest zgodny z istniejącym już interfejsem PacketListener i nie wymaga zastosowania odznaczonego rzutowania.

Należy pamiętać, że implementacja nie jest bezpieczna dla wątków, ponieważ implementacja addPacketListener wymaga synchronizacji na kluczu mapy (ponieważ potrzebny jest również oryginalny kod). Jednak enkapsulowanie listy detektorów pakietów w niezmiennej wersji DelegatingPacketListener jest prawdopodobnie lepiej dostosowane do celów współbieżności.

Powiązane problemy