2017-06-07 4 views
7

Je souhaite créer un IdentityHashMap<Class<T>, Consumer<T>>. Fondamentalement, je veux mapper un type avec une méthode indiquant quoi faire avec ce type.Consommateur <T> mappé Classe <T> dans HashMap

Je veux dynamiquement pouvoir dire avec des objets X, exécutez Y. Je peux faire

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

mais il suce parce que je dois jeter l'objet dans le lamba lorsque vous l'utilisez.

Exemple:

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

Ce que je voudrais faire est

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

Mais il ne semble pas être autorisé. Y a-t-il un moyen de faire cela ? Quelle est la meilleure solution pour mapper les types avec une méthode pour ce type?

+3

C'est un feu de position , mais notez que 'java.lang.Class' définit l'égalité en tant qu'identité, il n'est donc pas nécessaire d'utiliser' IdentityHashMap' ici. – ruakh

+0

@ruakh Il n'est pas non plus nécessaire d'utiliser 'HashMap'. Qu'est-ce que cela apporterait à avoir 2 appels supplémentaires «égaux» lors de la comparaison? – Winter

+1

Veuillez consulter http://docs.oracle.com/javase/8/docs/api/java/util/IdentityHashMap.html. Vous ne devez utiliser 'IdentityHashMap' que lorsque vous en avez spécifiquement besoin. – ruakh

Répondre

6

Il s'agit essentiellement comme le conteneur hétérogène de type sécurisé, peut-être à l'origine décrit par Joshua Bloch, sauf que vous ne pouvez pas utiliser le Class pour lancer le résultat.

Bizarrement, je ne peux pas trouver un excellent exemple existant sur le SO, alors voici une:

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

S'il est nécessaire d'utiliser des types génériques, comme Consumer<List<String>>, alors vous avez besoin d'utiliser quelque chose comme la goyave TypeToken car Class ne peut représenter que l'effacement d'un type.

Une chose gênant sur les limites des génériques de Java est que l'un de ceux-ci ne peut pas être écrit de manière générique, parce qu'il n'y a aucun moyen de le faire, par exemple:

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

Quoi qu'il en soit, c'est comment faire ce genre de chose .

En guise de note, Guava a un ClassToInstanceMap pour quand vous avez besoin d'un Map<Class<T>, T>.

1

Je peux juste laisser le IdentityHashMap avec l'habituel Class<?> et Consumer<?>

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

Et puis j'envelopper l'opération de vente dans une méthode. Cette méthode accepte un type et un consommateur du même générique.

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

Cela me permet d'écrire

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

Il est possible de mettre en œuvre cette manière de type sécurité sans aucune fonte sans contrôle. La solution réside dans l'emballage Consumer<T> dans une Consumer<Object> plus générale qui jette et délégués au consommateur d'origine:

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

En fonction de vos besoins, get() pourrait aussi revenir simplement Consumer<Object>. Cela serait nécessaire si vous ne connaissez que le type lors de l'exécution, par ex.

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

Je suis assez sûr d'avoir vu cette solution (ou quelque chose de similaire) dans un talk @ Devoxx Belgique 2016, peut-être de Venkat Subramaniam, mais je ne peux définitivement pas la retrouver ...

+0

C'est vraiment bien, mais ce n'est pas toujours une solution générale. Par exemple, il serait plus difficile d'utiliser ce modèle si nous avions une 'Map , List > ', ou une autre valeur pour laquelle nous ne pourrions pas facilement créer des adaptateurs. – Radiodef

+1

@radiodef en effet, ce sera toujours un cas par cas. Je souhaite qu'il y avait une manière plus propre de faire des typecasts génériques sans avertissement. –