2017-06-07 1 views
7

Ich möchte ein IdentityHashMap<Class<T>, Consumer<T>> erstellen. Grundsätzlich möchte ich einen Typ mit einer Methode zuordnen, die besagt, was mit diesem Typ zu tun ist.Verbraucher <T> gemappt Klasse <T> in HashMap

Ich möchte dynamisch der Lage sein, mit Objekten X zu sagen, führen Y. I

private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>(); 

tun können, aber es saugt, weil dann muss ich das Objekt in der lamba werfen, wenn es zu benutzen.

Beispiel:

interceptor.put(Train.class, train -> { 
    System.out.println(((Train)train).getSpeed()); 
}); 

Was Ich mag würde zu tun ist,

private <T> IdentityHashMap<Class<T>, Consumer<T>> interceptor = new IdentityHashMap<>(); 

Aber es scheint nicht erlaubt zu werden. Gibt es eine Möglichkeit, dies zu tun? Was ist die beste Problemumgehung, um Typen mit einer Methode für diesen Typ zuzuordnen?

+3

Dies ist ein Seitenlicht Beachten Sie jedoch, dass 'java.lang.Class' Gleichheit als Identität definiert. Daher ist es nicht notwendig,' IdentityHashMap' zu verwenden. – ruakh

+0

@ruakh Es ist auch nicht notwendig, 'HashMap' zu verwenden. Was würde es bringen, wenn man beim Vergleich zwei zusätzliche "Gleich" -Aufrufe hat? – Winter

+1

Siehe http://docs.oracle.com/javase/8/docs/api/java/util/IdentityHashMap.html. Sie sollten 'IdentityHashMap' nur verwenden, wenn Sie es benötigen. – ruakh

Antwort

6

schreiben Dies ist im Wesentlichen genauso wie die typsichere heterogenen Behälter, vielleicht ursprünglich von Joshua Bloch beschrieben, außer dass Sie nicht die Class können das Ergebnis werfen.

Weirdly, ich kann nicht ein großes Beispiel bestehende auf SO, finden, so ist hier ein:

package mcve; 
import java.util.*; 
import java.util.function.*; 

class ClassToConsumerMap { 
    private final Map<Class<?>, Consumer<?>> map = 
     new IdentityHashMap<>(); 

    public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) { 
     @SuppressWarnings("unchecked") 
     final Consumer<? super T> old = 
      (Consumer<? super T>) map.put(key, c); 
     return old; 
    } 

    public <T> Consumer<? super T> get(Class<T> key) { 
     @SuppressWarnings("unchecked") 
     final Consumer<? super T> c = 
      (Consumer<? super T>) map.get(key); 
     return c; 
    } 
} 

Wenn es notwendig ist, generische Typen zu verwenden, wie Consumer<List<String>>, dann müssen Sie etwas wie Guava TypeToken verwenden weil Class nur die Löschung eines Typs darstellen kann.

Eine ärgerliche Sache über die Grenzen von Generika Java ist, dass eine von ihnen nicht allgemein geschrieben werden kann, weil es keine Möglichkeit gibt, zum Beispiel zu tun:

class ClassToGenericValueMap<V> { 
    ... 
    public <T> V<T> put(Class<T> key, V<T> val) {...} 
    public <T> V<T> get(Class<T> key) {...} 
} 

Wie auch immer, das ist, wie diese Art der Sache zu tun .

Als eine Randnotiz hat Guava eine ClassToInstanceMap für wenn Sie eine Map<Class<T>, T> benötigen.

1

Ich kann nur die IdentityHashMap mit den üblichen Class<?> lassen und Consumer<?>

private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>(); 

Und dann der Put-Operation in einer Methode, die ich wickeln. Diese Methode akzeptiert einen Typ und einen Consumer desselben Generic.

public <T> void intercept(Class<T> type, Consumer<T> consumer) 
{ 
    interceptor.put(type, consumer); 
} 

Dies lässt mich

intercept(Train.class, train -> { 
    System.out.println(train.getSpeed()); 
}); 
5

Es ist möglich, dies in einer typsicheren Weise zu implementieren ohne jede unkontrollierte Besetzung. Die Lösung liegt die Consumer<T> in eine allgemeinere Consumer<Object> in Einwickeln, die werfen und dann die Delegierten den Erstkunden:

public class ClassToConsumerMap { 
    private final Map<Class<?>, Consumer<Object>> map = new IdentityHashMap<>(); 

    public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) { 
     return map.put(key, o -> c.accept(key.cast(o))); 
    } 

    public <T> Consumer<? super T> get(Class<T> key) { 
     return map.get(key); 
    } 
} 

Je nach Bedarf könnte get() auch einfach ein Consumer<Object> zurück. Dies wäre notwendig, wenn Sie den Typ nur zur Laufzeit kennen, z.

classToConsumerMap.get(someObject.getClass()).accept(someObject); 

Ich bin ziemlich sicher, dass ich diese Lösung sah (oder etwas ähnliches) in einem Vortrag @ Devoxx Belgien 2016, möglicherweise von Venkat Subramaniam, aber ich kann es definitiv nicht zurück finden ...

+0

Das ist wirklich gut, aber es ist nicht immer eine allgemeine Lösung. Zum Beispiel wäre es schwieriger, dieses Muster zu verwenden, wenn wir eine "Karte , Liste >" oder einen anderen Wert hätten, für den wir nicht einfach Adapter erstellen können. – Radiodef

+1

@radiodef tatsächlich, das wird immer ein Fall sein. Ich wünschte, es gäbe einen saubereren Weg, generische Typcasts ohne Vorwarnung zu machen. –

Verwandte Themen