2012-01-13 12 views
7

Il problema: Ho una funzione di interfaccia oggetto definito in una classe:Generics: come applicare restrizioni tra chiavi e valori in una mappa

public static interface FunctionObject<T> { 
     void process(T object); 
    } 

ho bisogno generico perché mi piacerebbe usare T metodi nelle implementazioni del processo.
Poi, in altre classe generica, ho una mappa dove ho classi come chiavi e oggetti funzione come valori:

Map<Class<T>, FunctionObject<T>> map; 

ma voglio anche la mappa di accettare le classi sottotipo e oggetti funzione di supertipi DEL Tipo chiave, così ho fatto questo:

Map<Class<? extends T>, FunctionObject<? super T>> map; //not what I need 

l'idea di base è quella di essere in grado di utilizzare la mappa come segue:

//if T were Number, this should be legal 
    map.put(Class<Integer>, new FunctionObject<Integer>(){...}); 
    map.put(Class<Float>, new FunctionObject<Number>(){...}); 
    map.put(Class<Double>, new FunctionObject<Object>(){...}); 

come voglio rispettare la FunctionObject ha il tipo di chiave classe o un supertipo, quello che mi piacerebbe davvero definire è questo:

Map<Class<E extends T>, FunctionObject<? super E>>> map; 

Come posso ottenere l'effetto desiderato? Un contenitore eterogeneo tipicamente è l'unica opzione? Come apparirebbero i tipi generici di Map per consentirne il popolamento da un riferimento?

risposta

4

contenitore parametrizzata, sembra funzionare bene:

public class MyMap<T> 
{ 
    interface FunctionObject<X> {} 

    private Map<Class<? extends T>, FunctionObject<Object>> map = new HashMap<>(); 

    @SuppressWarnings("unchecked") 
    public <E extends T> void put(Class<E> c, FunctionObject<? super E> f) 
    { 
     map.put(c, (FunctionObject<Object>) f); 
    } 

    public <E extends T> FunctionObject<Object> get(Class<E> c) 
    { 
     return map.get(c); 
    } 

    public static void Main(String[] args) 
    { 
     MyMap<Number> map = new MyMap<>(); 

     map.put(Integer.class, new FunctionObject<Integer>() {}); 
     map.put(Float.class, new FunctionObject<Number>() {}); 
     map.put(Double.class, new FunctionObject<Object>() {}); 
    } 
} 

A cura per conformarsi alla domanda. Purtroppo non c'è modo di evitare il downcasting di opporsi.

Modifica aggiunto get().

+0

Potrebbe funzionare in base al caso d'uso. –

+0

Sì, ma implementare l'acquisizione è quando inizia il divertimento XD. –

+0

'get()' può sempre restituire 'FunctionObject ()': poiché il parametro è una * superclasse * di T e la chiave, non c'è modo di stabilire un limite superiore. L'ho aggiunto alla risposta. – Viruzzo

4

È possibile eseguire questa operazione con l'incapsulamento, assumendo che si utilizzi solo la mappa tramite il metodo che verifica questo a base di voce.

Il seguente metodo di aggiunta evita la necessità di raddoppiare il tipo.

public class Main { 
interface FunctionObject<T> { } 

private final Map<Class, FunctionObject> map = new LinkedHashMap<Class, FunctionObject>(); 

public <T> void add(FunctionObject<T> functionObject) { 
    Class<T> tClass = null; 
    for (Type iType : functionObject.getClass().getGenericInterfaces()) { 
     ParameterizedType pt = (ParameterizedType) iType; 
     if (!pt.getRawType().equals(FunctionObject.class)) continue; 
     Type t = pt.getActualTypeArguments()[0]; 
     tClass = (Class<T>) t; 
     break; 
    } 
    map.put(tClass, functionObject); 
} 

public <T> void put(Class<T> tClass, FunctionObject<T> functionObject) { 
    map.put(tClass, functionObject); 
} 

public <T> FunctionObject<T> get(Class<T> tClass) { 
    return map.get(tClass); 
} 

public static void main(String... args) throws IOException { 
    Main m = new Main(); 
    m.add(new FunctionObject<Integer>() { 
    }); 
    FunctionObject<Integer> foi = m.get(Integer.class); 
    System.out.println(foi.getClass().getGenericInterfaces()[0]); 
} 
} 

stampe

Main.Main$FunctionObject<java.lang.Integer> 

È possibile utilizzare @SuppressWarnings("unchecked") se si desidera disattivare l'avviso.

Il punto è; non c'è modo di descrivere il vincolo che si ha nella dichiarazione di campo, è possibile ottenere lo stesso risultato se si utilizzano metodi di accesso che eseguono il controllo su base per voce. È possibile aggiungere anche i controlli di runtime se è necessario assicurarsi che i tipi non elaborati siano corretti.

+0

Ma questo consente solo il tipo T e niente di più, non è vero? E a proposito, quella dichiarazione Map fornisce un avvertimento per l'utilizzo di tipi non elaborati. –

+5

Sì, questo è il punto della risposta di Peter: non è la mappa che garantisce la sicurezza del tipo. È il fatto che l'unico modo per inserire/ottenere valori in/dalla mappa è utilizzare questi metodi, che associano sempre una Funtion a una Classe . Naturalmente, se si utilizzano i tipi non elaborati quando si chiamano questi metodi, non si ha alcuna garanzia. –

+0

Ok, quindi non c'è altra scelta che codificare un contenitore di tipo "types". Mi chiedo se questo è lo stesso in C#. –

Problemi correlati