我想创建一个IdentityHashMap<Class<T>, Consumer<T>>。基本上,我想使用一种说明该类型的方法来映射一个类型。
IdentityHashMap<Class<T>, Consumer<T>>
我希望能够动态地说出对象X,执行Y。我可以
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();
但它很烂,因为然后我必须在使用它时将对象投射到兰巴中。
例:
interceptor.put(Train.class, train -> { System.out.println(((Train)train).getSpeed()); });
我想做的是
private <T> IdentityHashMap<Class<T>, Consumer<T>> interceptor = new IdentityHashMap<>();
但这似乎是不允许的。有没有办法做到这一点 ?使用此类型的方法映射类型的最佳解决方法是什么?
本质上,这与Joshua Bloch所描述的类型安全的异构容器类似,只是您不能使用Class来强制转换结果。
Class
奇怪的是,我找不到SO上存在的一个很好的例子,所以这是一个例子:
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); } }
这是类型安全的,因为键和值之间的关系由put方法的签名强制执行。
put
关于Java泛型的局限性,一件令人讨厌的事情是这些容器之一不能为泛型值类型编写,因为无法执行例如:
class ClassToGenericValueMap<V> { ... public <T> V<T> put(Class<T> key, V<T> val) {...} public <T> V<T> get(Class<T> key) {...} }
其他说明:
我会用一个常规的HashMap或LinkedHashMap本。HashMap维护得更好,并且有许多IdentityHashMap没有的优化。
HashMap
LinkedHashMap
IdentityHashMap
如果有必要使用通用类型(如)Consumer<List<String>>,则需要使用类似Guava TypeToken的键作为键,因为Class它只能表示擦除类型。
Consumer<List<String>>
TypeToken
番石榴ClassToInstanceMap在需要的时候可以食用Map<Class<T>, T>。
ClassToInstanceMap
Map<Class<T>, T>
有时人们想通过类对消费者的映射来做这样的事情:
public <T> void accept(T obj) { Consumer<? super T> c = get(obj.getClass()); if (c != null) c.accept(obj); }
也就是说,给定任何对象,请在映射中找到与该对象的类绑定的使用者,并将该对象传递给使用者的accept方法。
accept
这个例子就不能编译,但是,因为getClass()实际上被指定为返回Class<? extends |T|>,这里|T|是指 删除 的T。(请参阅JLS§4.3.2。)在上面的示例中,擦除Tis Object,因此obj.getClass()返回一个plain Class<?>。
getClass()
Class<? extends |T|>
|T|
T
Object
obj.getClass()
Class<?>
可以使用捕获助手方法解决此问题:
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)); }
另外,如果您希望修改后的版本get返回任何适用的使用者,则可以使用类似以下的内容:
get
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; }
这样,我们就可以在地图中放置一般的超型消费者,如下所示:
ctcm.put(Object.class, System.out::println);
然后使用子类型类进行检索:
Consumer<? super String> c = ctcm.findApplicable(String.class); c.accept("hello world");
这是一个更通用的示例,这次使用UnaryOperator并且没有边界通配符:
UnaryOperator
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); } }
第? super一个示例中的有界通配符特定于消费者,我认为不带通配符的示例可能更易于阅读。
? super