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 put(Class key, Consumer c) { return (Consumer) map.put(key, c); } @SuppressWarnings("unchecked") public  Consumer get(Class key) { return (Consumer) map.get(key); } } 

这是类型安全的,因为键和值之间的关系是由put方法的签名强制执行的。

关于Javagenerics的局限性的一个令人讨厌的事情是,这些容器中的一个不能为通用值类型编写,因为没有办法做到,例如:

 class ClassToGenericValueMap { ... public  V put(Class key, V val) {...} public  V get(Class key) {...} } 

其他说明:

  • 我会使用常规的HashMapLinkedHashMapHashMap得到了更好的维护,并且具有许多IdentityHashMap没有的优化。

  • 如果需要使用generics类型,例如Consumer> ,那么您需要使用类似Guava TypeToken的键作为键,因为Class只能表示类型的擦除。

  • 当您需要Map, T>时,Guava有一个ClassToInstanceMap

有时人们希望通过类到消费者的地图做这样的事情:

 public  void accept(T obj) { Consumer c = get(obj.getClass()); if (c != null) c.accept(obj); } 

也就是说,给定任何对象,找到绑定到该对象类的映射中的使用者,并将该对象传递给使用者的accept方法。

但是,这个例子不会编译,因为getClass()实际上被指定为返回一个Class Class ,其中|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 c = get(key); if (c != null) c.accept(key.cast(obj)); } 

此外,如果您想要一个返回任何适用的消费者的get的修改版本,您可以使用以下内容:

 public  Consumer findApplicable(Class key) { Consumer c = get(key); if (c == null) { for (Map.Entry, Consumer> e : map.entrySet()) { if (e.getKey().isAssignableFrom(key)) { @SuppressWarnings("unchecked") Consumer value = (Consumer) e.getValue(); c = value; break; } } } return c; } 

这让我们可以将普通的超类型消费者放在地图中,如下所示:

 ctcm.put(Object.class, System.out::println); 

然后使用子类型检索:

 Consumer 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> map = new IdentityHashMap<>(); public  Consumer put(Class key, Consumer c) { return map.put(key, o -> c.accept(key.cast(o))); } public  Consumer get(Class key) { return map.get(key); } } 

根据您的需要, get()也可以简单地返回Consumer 。 如果您只在运行时知道类型,那么这是必要的,例如

 classToConsumerMap.get(someObject.getClass()).accept(someObject); 

我很确定我在2016年Devoxx比利时的演讲中看到了这个解决方案(或类似的东西),可能来自Venkat Subramaniam,但我肯定找不到它…

我可以让IdentityHashMap使用通常的ClassConsumer

 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()); });