这基本上就像type-safe heterogeneous container described by Joshua Bloch,只是你不能使用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 方法的签名强制执行。
关于 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作为key,因为Class只能代表一个类型的擦除。
Guava 有一个 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 方法。
不过,该示例无法编译,因为 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,我认为没有通配符的示例可能更易于阅读。