Java – consumer mapping class in HashMap
I want to create an identityhashmap < class < T >, consumer < T > > Basically, I want to map a type in a way that shows how to handle this type
I want to be able to dynamically say with object x and execute y. I can do it
private IdentityHashMap<Class<?>,Consumer<?>> interceptor = new IdentityHashMap<>();
But it's bad because I have to project objects in Lamba when I use it
Example:
interceptor.put(Train.class,train -> { System.out.println(((Train)train).getSpeed()); });
What I want to do is
private <T> IdentityHashMap<Class<T>,Consumer<T>> interceptor = new IdentityHashMap<>();
But it seems that this is not allowed Is there any way to do this? What is the best solution for mapping types using this type of method?
Solution
This is basically like type safe heterogeneous container described by Joshua Bloch, except that you can't use class to force the result
Strangely, I can't find a good example of so, so here's one:
package mcve; import java.util.*; import java.util.function.*; class ClassToConsumerMap { private final Map<Class<?>,Consumer<?>> map = new HashMap<>(); @SuppressWarnings("unchecked") public <T> Consumer<? super T> put(Class<T> key,Consumer<? super T> c) { return (Consumer<? super T>) map.put(key,c); } @SuppressWarnings("unchecked") public <T> Consumer<? super T> get(Class<T> key) { return (Consumer<? super T>) map.get(key); } }
This is type safe because the relationship between keys and values is enforced by the signature of the put method
One annoying thing about the limitations of Java generics is that one of these containers cannot be written for a common value type because there is no way to do it, such as:
class ClassToGenericValueMap<V> { ... public <T> V<T> put(Class<T> key,V<T> val) {...} public <T> V<T> get(Class<T> key) {...} }
Other instructions:
>I will use regular HashMap or LinkedHashMap HashMap is better maintained and has many optimizations that identityhashmap does not have. > If you need to use generic types, such as consumer < list < string > >, you need to use a key similar to guava typetoken as the key, because class can only represent the erasure of types. > When you need map < class < T >, t >, guava has a classtoinstancemap
Sometimes people want to do this through the class to consumer map:
public <T> void accept(T obj) { Consumer<? super T> c = get(obj.getClass()); if (c != null) c.accept(obj); }
That is, given any object, find the consumer in the mapping bound to the object class and pass the object to the consumer's accept method
However, this example will not compile because getClass () is actually specified to return class , where | t | indicates the erasure of T (see JLS § 4.3.2.) In the above example, the erasure of T is object, so obj Getclass() returns an ordinary class
This problem can be solved through the capturing helper method:
public void accept(Object obj) { accept(obj.getClass(),obj); } private <T> void accept(Class<T> key,Object obj) { Consumer<? super T> c = get(key); if (c != null) c.accept(key.cast(obj)); }
In addition, if you want a modified version of get that returns any applicable consumer, you can use the following:
public <T> Consumer<? super T> findApplicable(Class<T> key) { Consumer<? super T> c = get(key); if (c == null) { for (Map.Entry<Class<?>,Consumer<?>> e : map.entrySet()) { if (e.getKey().isAssignableFrom(key)) { @SuppressWarnings("unchecked") Consumer<? super T> value = (Consumer<? super T>) e.getValue(); c = value; break; } } } return c; }
This allows us to put ordinary supertype consumers on the map, as shown below:
ctcm.put(Object.class,System.out::println);
Then use subclasses to retrieve:
Consumer<? super String> c = ctcm.findApplicable(String.class); c.accept("hello world");
This is a slightly more general example. This time, unaryoperator is used and there is no bounded wildcard:
package mcve; import java.util.*; import java.util.function.*; public class ClassToUnaryOpMap { private final Map<Class<?>,UnaryOperator<?>> map = new HashMap<>(); @SuppressWarnings("unchecked") public <T> UnaryOperator<T> put(Class<T> key,UnaryOperator<T> op) { return (UnaryOperator<T>) map.put(key,op); } @SuppressWarnings("unchecked") public <T> UnaryOperator<T> get(Class<T> key) { return (UnaryOperator<T>) map.get(key); } }
The ? Super bounded wildcard in the first example is specific to consumers