Consumer 在HashMap中映射了Class
我想创建一个IdentityHashMap<Class, Consumer>
。 基本上,我想用一种方法来映射一个类型,说明如何处理这种类型。
我想动态地能够用对象X说出来,执行Y.我能做到
private IdentityHashMap<Class, Consumer> interceptor = new IdentityHashMap();
但它很糟糕,因为那时我必须在使用它时将对象投射到lamba中。
例:
interceptor.put(Train.class, train -> { System.out.println(((Train)train).getSpeed()); });
我想做的是
private IdentityHashMap<Class, Consumer> interceptor = new IdentityHashMap();
但似乎不允许这样做。 有没有办法做到这一点 ? 使用此类型的方法映射类型的最佳解决方法是什么?
这基本上就像Joshua Bloch描述的类型安全异构容器一样 ,除了你不能使用Class
来强制转换结果。
奇怪的是,我找不到SO上存在的一个很好的例子,所以这里有一个:
package mcve; import java.util.*; import java.util.function.*; class ClassToConsumerMap { private final Map, Consumer>> map = new HashMap<>(); @SuppressWarnings("unchecked") public Consumer super T> put(Class key, Consumer super T> c) { return (Consumer super T>) map.put(key, c); } @SuppressWarnings("unchecked") public Consumer super T> get(Class key) { return (Consumer super T>) map.get(key); } }
这是类型安全的,因为键和值之间的关系是由put
方法的签名强制执行的。
关于Javagenerics的局限性的一个令人讨厌的事情是,这些容器中的一个不能为通用值类型编写,因为没有办法做到,例如:
class ClassToGenericValueMap { ... public V put(Class key, V val) {...} public V get(Class key) {...} }
其他说明:
-
我会使用常规的
HashMap
或LinkedHashMap
。HashMap
得到了更好的维护,并且具有许多IdentityHashMap
没有的优化。 -
如果需要使用generics类型,例如
Consumer
,那么您需要使用类似Guava- >
TypeToken
的键作为键,因为Class
只能表示类型的擦除。 -
当您需要
Map
时,Guava有一个, T> ClassToInstanceMap
。
有时人们希望通过类到消费者的地图做这样的事情:
public void accept(T obj) { Consumer super T> c = get(obj.getClass()); if (c != null) c.accept(obj); }
也就是说,给定任何对象,找到绑定到该对象类的映射中的使用者,并将该对象传递给使用者的accept
方法。
但是,这个例子不会编译,因为getClass()
实际上被指定为返回一个Class extends |T|>
Class extends |T|>
,其中|T|
意味着T
的擦除 。 (参见JLS§4.3.2 。)在上面的例子中, T
的擦除是Object
,所以obj.getClass()
返回一个普通的Class>
。
使用捕获帮助器方法可以解决此问题:
public void accept(Object obj) { accept(obj.getClass(), obj); } private void accept(Class key, Object obj) { Consumer super T> c = get(key); if (c != null) c.accept(key.cast(obj)); }
此外,如果您想要一个返回任何适用的消费者的get
的修改版本,您可以使用以下内容:
public Consumer super T> findApplicable(Class key) { Consumer super T> c = get(key); if (c == null) { for (Map.Entry, 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, UnaryOperator>> map = new HashMap<>(); @SuppressWarnings("unchecked") public UnaryOperator put(Class key, UnaryOperator op) { return (UnaryOperator ) map.put(key, op); } @SuppressWarnings("unchecked") public UnaryOperator get(Class key) { return (UnaryOperator ) map.get(key); } }
的? super
第一个示例中的? super
有界通配符特定于消费者 ,我认为没有通配符的示例可能更容易阅读。
可以以类型安全的方式实现它, 而无需任何未经检查的强制转换 。 解决方案在于将Consumer
包装到一个更通用的Consumer
中,然后委托给原始使用者:
public class ClassToConsumerMap { private final Map, Consumer
根据您的需要, get()
也可以简单地返回Consumer
。 如果您只在运行时知道类型,那么这是必要的,例如
classToConsumerMap.get(someObject.getClass()).accept(someObject);
我很确定我在2016年Devoxx比利时的演讲中看到了这个解决方案(或类似的东西),可能来自Venkat Subramaniam,但我肯定找不到它…
我可以让IdentityHashMap
使用通常的Class>
和Consumer>
private IdentityHashMap, Consumer>> interceptor = new IdentityHashMap<>();
然后我将put操作包装在一个方法中。 此方法接受相同generics的类型和使用者。
public void intercept(Class type, Consumer consumer) { interceptor.put(type, consumer); }
这让我写
intercept(Train.class, train -> { System.out.println(train.getSpeed()); });