2015-05-22 9 views
8

मुझे हमेशा जेनिक्स का उपयोग संग्रह और वाइल्डकार्ड के साथ कठिन समय होता है।जेनेरिक हैंडलर के जेनेरिक मल्टीटाइप संग्रह की घोषणा कैसे करें

तो यहां निम्नलिखित नक्शा है। मैं एक विशिष्ट प्रकार के पैकेट वर्ग के लिए हैंडलर का संग्रह रखना चाहता हूं।

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

पीईसीएस सिद्धांत का पालन करने का प्रयास करें। यह आपकी चीजों को आसान बना देगा। http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs – Pranalee

+0

कृपया यह न करें कि आप 'ConcurrentMap' का उपयोग करते हैं, फिर भी' addPacketListener' में कोड थ्रेड सुरक्षित नहीं है और मानचित्र पर सिंक्रनाइज़ेशन की आवश्यकता होगी कुंजी। – SpaceTrucker

+0

@ स्पेसट्रकर वास्तव में क्यों है? – Antoniossss

उत्तर

4

वहाँ एक अच्छा छवि है: PECS अन्य answers जो आप इस समस्या को समझा सकते हैं में से एक में।

बात पी ई सी एस कहा जाता है, जिसके लिए

निर्माता extends और उपभोक्ता super खड़ा है।

टी एल; डॉ: यदि आप केवल दोनों add और get एक ठोस प्रकार (T) के साथ एक संग्रह करने के लिए से/कर सकते हैं। के साथ आप T (और इसके संभावित उपप्रकार) प्राप्त कर सकते हैं और आप Collection पर के साथ जोड़ सकते हैं लेकिन आप दोनों तरीकों से नहीं जा सकते हैं: इस प्रकार आपकी त्रुटियां।

+1

यह कमाल है! – Ian2thedv

+0

ध्यान दें कि छवि मेरे द्वारा खींची नहीं गई थी: एक विस्तृत विस्तृत उत्तर के लिए मैंने जो लिंक साबित किया था उसे जांचें। –

1

आपकी समस्या यहां शुरू होता है:

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

आप एक साथ दो ? बाध्य करने के लिए इतना है कि प्रकार Class<T> की एक प्रमुख के साथ एक देखने के एक मूल्य में परिणाम होगा एक तरह से करने के लिए उम्मीद कर रहे हैं (या शायद सिर्फ उम्मीद कर रहा) टाइप करें List<PacketListener<T>>। अफसोस की बात है कि जावा को बताने का कोई तरीका नहीं है कि दो ? समान हैं लेकिन अलग-अलग (लेकिन बाधित) प्रकार ले सकते हैं।

यह समस्या आमतौर पर covariance/contravariance विधियों का उपयोग करके हल की गई है, लेकिन आपके मामले में आपको और दोनों को अपने संग्रह से पढ़ने की आवश्यकता है। इसलिए आप invariance का उपयोग करना चाहिए।

मेरा मानना ​​है कि आपकी समस्या का समाधान दो वस्तुओं को एक सहायक वर्ग में बांधना है और इसलिए वहां पर आविष्कार शुरू करना है। इस तरह आप उन्हें समानता बनाए रख सकते हैं जबकि उन्हें अभी भी प्रतिबंधों के तहत बदलना है।

इनमें से कुछ थोड़ा हैकी आईएमएचओ (यानी कुछ जानवर हैं) लेकिन कम से कम आप अपना लक्ष्य प्राप्त कर सकते हैं और आप अभी भी सुरक्षित हैं। जानवरों को काफी मान्य हैं।

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> वस्तुओं मानचित्र में उनके Class<T> की कुंजी है और इसलिए संलग्न सूची वास्तव में List<PacketListener<T> है।

0

निम्नलिखित @OldCurmudgeon के उत्तर के समान है।

कुंजीपटल listeners फ़ील्ड भी है। लेकिन मैं इसे इस प्रकार घोषित करता हूं:

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

यहां बिंदु यह है कि हम मानचित्र मूल्य प्रकार के रूप में सूची से छुटकारा पा सकते हैं।

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

कृपया ध्यान दें कि प्रकार पैरामीटर T में उपयोग नहीं किया जाता है:

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 में से एक अधिक विशिष्ट कार्यान्वयन की जरूरत का समर्थन करता है: DelegatingPacketListener इस प्रकार घोषित किया जाता है उपकरण लागू करता है। यह केवल कार्यान्वयन के लिए उपयोग किया जाता है। हम प्रत्येक PacketListener को 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 जो अब एक सामान्य प्रकार का उपयोग नहीं करता है के लिए बदल गया है।

OldCurmudgeon के समाधान की तुलना में, यह पहले से मौजूद PacketListener इंटरफ़ेस के साथ चिपक जाता है और इसे अनचेक कास्ट लागू करने की आवश्यकता नहीं होती है।

ध्यान दें कि कार्यान्वयन मानचित्र कुंजी पर addPacketListener जरूरतों तुल्यकालन के अंतर्गत प्रयोग की वजह से सुरक्षित थ्रेड नहीं है, (के रूप में प्रश्न में मूल कोड भी जरूरत है)। हालांकि अपरिवर्तनीय DelegatingPacketListener में पैकेट श्रोताओं की सूची को समाहित करना शायद समवर्ती उद्देश्यों के लिए बेहतर अनुकूल है।

संबंधित मुद्दे