2015-05-06 14 views
5

Nicht sicher, ob das überhaupt möglich ist; Ich gebe zu, ich bin nicht so gut in Generika, wie ich gerne wäre.Java 8 generische Karte der Funktionen

Grundsätzlich möchte ich eine Karte von Klasse erstellen -> Funktion wo die Klasse für den Schlüssel verwendet wird, ist die Klasse der Eingabe in die Funktion, wie solche (nicht legal Syntax):

public static Map<Class<T>,Function<T,Expression>> STUFF = new HashMap<>(); 

{ 
    STUFF.put(List.class, ListExpression::new); 
    STUFF.put(String.class, StringExpression::new);// this constructor must take string 
} 

so dass, wenn ich das tue:

Function<String,Expression> e = STUFF.get(o.getClass()); 
Expression ex = e.apply(o); 

Es bekommt die Typen richtig für mich.

+0

Mögliche Duplikat http://stackoverflow.com/questions/10194855/java-map-key-class-value-instance-of-that-class –

+0

Ich glaube, Sie brauchen Übernehmen Sie 'Put' in HashMap, um das zu tun. – njzk2

Antwort

6

Es ist nicht möglich. Jedes Mal, wenn Sie Map<Class<T>, SomeType<T>> möchten, dh eine Klasse mit einem parametrisierten Typ assoziieren, der irgendwie mit der Klasse im Schlüssel zusammenhängt, kann dies nicht geschehen. Dies liegt daran, dass der Schlüsseltyp Class<T> unter allen Einträgen gemäß der Definition Map<K, V> geteilt werden muss.

Was übrig bleibt, ist die praktische Alternative zu einem Map<Class<?>, SomeType<?>>, kapseln Sie diese Karte in einem privaten Feld ein und überprüfen Sie Einschränkungen, wenn Sie Elemente in der Karte platzieren. Etwas wie

public class StuffManager { 

    private final Map<Class<?>, Consumer<?>> stuff = new HashMap<>(); 

    public <T> void register(Class<T> key, Consumer<? super T> val) { 
    stuff.put(key, val); 
    } 
} 
+0

Gibt es einen Grund, warum ich nicht nur StuffManager Klasse Klasse, Funktion dann Delegierung Klasse? –

+0

@Christian Bongiorno Sie können keinen Typ Parameter im Manager haben, weil jeder Eintrag in der Karte eine eigene, andere Klasse hat – Raffaele

+0

@Radiodef Ich glaube nicht. Wenn Sie Ihren Ausdruck über Object.class erhalten, wird dieser Ausdruck gemäß Ihrem Beispiel als String ausgewertet, was objektkompatibel ist, daher wird keine ClassCastException ausgelöst. Übrigens, PECS ist einfach dumm: Es wurde erfunden, um Sammlungen nur Leuten zu erklären, die Generika nicht verstehen konnten, aber es gibt hier keine Sammlungen (Karte ist irrelevant). Ich denke, "super" kann auch sinnvoll sein, aber es hängt alles von den tatsächlichen Typen des OP-Programms ab. – Raffaele

1

Nein, Sie können dies nicht mit der Standardschnittstelle Map tun.

Aber man kann natürlich auch ‚verstecken‘ die Karte hinter einer Fassade - eine benutzerdefinierte Schnittstelle oder Klasse, die spezifischen Methoden für den Benutzer bietet:

public <T> Function<T, Expression> getFunction(Class<T> key) { 
    // implement using generic Map<Class<?>, Function<?, Expression> 
} 

oder sogar:

public Expression apply(Object param); 
3

Wenn Sie Verwenden Sie etwas wie Guavas TypeToken, dann können Sie dies zwar typsicher tun, aber immer noch unkontrolliert.

class ExpressionMap { 
    private final Map<TypeToken<?>, Function<?, Expression>> m = 
     new HashMap<>(); 

    <T> void put(TypeToken<T> type, Function<T, Expression> f) { 
     m.put(type, f); 
    } 

    <T> Function<T, Expression> get(TypeToken<T> type) { 
     @SuppressWarnings("unchecked") 
     final Function<T, Expression> f = 
      (Function<T, Expression>) m.get(type); 
     return f; 
    } 
} 

static ExpressionMap stuff = ExpressionMap(); 
static { 
    stuff.put(new TypeToken<List>() {}, ListExpression::new); 
    stuff.put(new TypeToken<String>() {}, StringExpression::new); 
} 

können Sie einen Class statt TypeToken verwenden, aber das Problem ist, dass es mit generischen Typen zusammenbricht.

Wenn Sie eine

hatte
ListStringExpression extends Expression { 
    ListStringExpression(List<String> l) {} 
} 

Sie können nicht haben ein Class<List<String>> so alle ListOfSomeTypeExpression::new zusammen als Function<List, Expression> in einen Topf geworfen zu bekommen. Es ist nicht sicher.

Sie können dies tun:

ExpressionMap em = new ExpressionMap(); 
em.put(List.class, ListStringExpression::new); 
// causing heap pollution 
// subtly passing List<Integer> to List<String> 
Expression ex = 
    em.get(List.class).apply(Arrays.asList(1, 2, 3)); 

So kann es aber die Vorbehalte in Acht nehmen.


Siehe auch What is a raw type and why shouldn't we use it?