java - generic - Clase asociada<T> mapeada<T> en HashMap
java generics (3)
Es posible implementar esto de forma segura sin ningún tipo de conversión no verificada . La solución reside en envolver Consumer<T>
en un Consumer<Object>
más general que emite y luego delega al consumidor original:
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);
}
}
Dependiendo de sus necesidades, get()
también podría simplemente devolver un Consumer<Object>
. Esto sería necesario si solo conoce el tipo en tiempo de ejecución, por ejemplo
classToConsumerMap.get(someObject.getClass()).accept(someObject);
Estoy bastante seguro de haber visto esta solución (o algo similar) en una charla @ Devoxx Belgium 2016, posiblemente de Venkat Subramaniam, pero definitivamente no puedo encontrarla de nuevo ...
Quiero crear un IdentityHashMap<Class<T>, Consumer<T>>
. Básicamente, quiero asignar un tipo con un método que diga qué hacer con este tipo.
Quiero poder decir dinámicamente con los objetos X, ejecutar Y. Puedo hacer
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();
pero apesta porque entonces tengo que lanzar el objeto en el lamba cuando lo uso.
Ejemplo:
interceptor.put(Train.class, train -> {
System.out.println(((Train)train).getSpeed());
});
Lo que me gustaría hacer es
private <T> IdentityHashMap<Class<T>, Consumer<T>> interceptor = new IdentityHashMap<>();
Pero no parece estar permitido. Hay alguna forma de hacer esto ? ¿Cuál es la mejor solución para mapear tipos con un método para este tipo?
Puedo dejar el IdentityHashMap
con la Class<?>
Y el Consumer<?>
Habituales
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();
Y luego envuelvo la operación de poner en un método. Este método acepta un tipo y un consumidor del mismo genérico.
public <T> void intercept(Class<T> type, Consumer<T> consumer)
{
interceptor.put(type, consumer);
}
Esto me permite escribir
intercept(Train.class, train -> {
System.out.println(train.getSpeed());
});
Esto es esencialmente igual que el contenedor heterogéneo tipo seguro, tal vez originalmente descrito por Joshua Bloch, excepto que no puede usar la Class
para emitir el resultado.
Extrañamente, no puedo encontrar un gran ejemplo que exista en SO, así que aquí hay uno:
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;
}
}
Si es necesario usar tipos genéricos, como Consumer<List<String>>
, entonces necesita usar algo como Guava TypeToken
porque Class
solo puede representar el borrado de un tipo.
Una cosa molesta acerca de las limitaciones de los genéricos de Java es que uno de estos no se puede escribir genéricamente, porque no hay manera de hacerlo, por ejemplo:
class ClassToGenericValueMap<V> {
...
public <T> V<T> put(Class<T> key, V<T> val) {...}
public <T> V<T> get(Class<T> key) {...}
}
De todos modos, así es como hacer este tipo de cosas.
Como nota al margen, Guava tiene un ClassToInstanceMap
para cuando necesitas un Map<Class<T>, T>
.