我想动态地能够用对象X说,执行Y.我能做到
private IdentityHashMap<Class<?>,Consumer<?>> interceptor = new IdentityHashMap<>();
但它很糟糕,因为我在使用它时必须在lamba中投射对象.
例:
interceptor.put(Train.class,train -> { System.out.println(((Train)train).getSpeed()); });
我想做的是
private <T> IdentityHashMap<Class<T>,Consumer<T>> interceptor = new IdentityHashMap<>();
解决方法
奇怪的是,我找不到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方法的签名强制执行的.
关于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所没有的优化.
>如果需要使用泛型类型,例如Consumer< List< String>>,则需要使用类似Guava TypeToken
的键作为键,因为Class只能表示类型的擦除.
>当你需要Map< Class< T>,T>时,Guava有一个ClassToInstanceMap
.
有时人们希望通过类到消费者的地图做这样的事情:
public <T> void accept(T obj) { Consumer<? super T> c = get(obj.getClass()); if (c != null) c.accept(obj); }
也就是说,给定任何对象,找到绑定到该对象类的映射中的使用者,并将该对象传递给使用者的accept方法.
但是,该示例不会编译,因为getClass()实际上被指定为返回Class<? extends | T |>,其中| T |表示T的擦除(参见JLS §4.3.2.)在上面的例子中,T的擦除是Object,所以obj.getClass()返回一个普通的Class<?>.
这个问题可以通过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)); }
此外,如果您想要一个返回任何适用消费者的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并且没有有界通配符:
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,我认为没有通配符的例子可能更容易阅读.