我想创建一个地图,它将提供泛型的好处,同时支持多种不同类型的值。我认为以下是通用集合的两个关键优势:
所以我想要的是地图:
使用泛型的基本情况是:
Map<MyKey, Object> map = new HashMap<MyKey, Object>();
// No type checking on put();
map.put(MyKey.A, "A");
map.put(MyKey.B, 10);
// Need to cast from get();
Object a = map.get(MyKey.A);
String aStr = (String) map.get(MyKey.A);
我找到了一种方法来解决第二个问题,方法是创建一个AbstractKey,它由与此键相关联的值类别生成:
public interface AbstractKey<K> {
}
public enum StringKey implements AbstractKey<String>{
A,B;
}
public enum IntegerKey implements AbstractKey<Integer>{
C,D;
}
然后我可以创建一个TypedMap,并覆盖put()和get()方法:
public class TypedMap extends HashMap<AbstractKey, Object> {
public <K> K put(AbstractKey<K> key, K value) {
return (K) super.put(key, value);
}
public <K> K get(AbstractKey<K> key){
return (K) super.get(key);
}
}
这允许以下内容:
TypedMap map = new TypedMap();
map.put(StringKey.A, "A");
String a = map.get(StringKey.A);
但是,如果我为密钥输入了错误的值,我不会收到任何编译错误。相反,我在get()上获得了运行时ClassCastException
。
map.put(StringKey.A, 10); // why doesn't this cause a compile error?
String a = map.get(StringKey.A); // throws a ClassCastException
如果这个.put()可能会产生编译错误,那将是理想的。
作为当前第二好的,我可以在put()方法中抛出运行时ClassCastException
。
// adding this method to the AbstractKey interface:
public Class getValueClass();
// for example, in the StringKey enum implementation:
public Class getValueClass(){
return String.class;
}
// and override the put() method in TypedMap:
public <K> K put(AbstractKey<K> key, K value){
Object v = key.getValueClass().cast(value);
return (K) super.put(key, v);
}
现在,将ClassCastException
放入地图时会抛出,如下所示。这是更可取的,因为它允许更容易/更快的调试来识别将不正确的键/值组合放入TypedMap的位置。
map.put(StringKey.A, 10); // now throws a ClassCastException
所以,我想知道:
map.put(StringKey.A, 10)
不会导致编译错误?我如何调整此设计以在put上获得有意义的编译错误,其中值不是密钥的关联泛型类型?
这是一个合适的设计来实现我想要的(见上)? (任何其他想法/意见/警告也将不胜感激......)
我是否有可以用来实现我想要的替代设计?
编辑 - 澄清:
答案 0 :(得分:4)
第29项:考虑类型安全的异构容器。 - Joshua Bloch, Effective Java, Second Edition, Chapter 5: Generics。
答案 1 :(得分:3)
你正在搞乱泛型和过载。您正在扩展HashMap<AbstractKey, Object>
,因此您的类继承了方法Object put(AbstractKey k, Object v)
。在您的班级中,您正在使用不同的签名定义另一个put
方法,这意味着您只是重载put
方法,而不是覆盖它。
编写map.put(StringKey.A, 10)
时,编译器会尝试查找符合参数类型put(StringKey, Integer)
的方法。您的方法的签名不适用,但继承的put
确实 - StringKey
与AbstractKey
兼容,Integer
与Object
兼容。因此,它将该代码编译为HashMap.put
的调用。
解决此问题的方法:将put
重命名为某个自定义名称,例如typedPut
。
答案 2 :(得分:1)
Value
类型。像这样:
public class Value {
private enum Type {
STRING, INTEGER;
}
private Type type;
private Object value;
private Value(Object value, Type type) {
this.value = value;
this.type = type;
}
public static Value fromString(String s) {
return new Value(s, Type.STRING);
}
public static Value fromInteger(Integer i) {
return new Value(i, Type.INTEGER);
}
public Type getType() {
return this.type;
}
public String getStringValue() {
return (String) value;
}
public Integer getIntegerValue() {
return (Integer) value;
}
// equals, hashCode
}
这样,您只需要一个Map<SomeKey, Value>
,就可以安全地从地图中获取值:
Value v = map.get(someKey);
if (v.getType() == Type.STRING) {
String s = v.getStringValue();
}
else if (v.getType() == Type.INTEGER) {
Integer i = v.getIntegerValue();
}
答案 3 :(得分:0)
考虑一下:(感谢有效的java)
public class TypedMap {
private Map<AbstractKey<?>, Object> map = new HashMap<AbstractKey<?>, Object>();
public <T> T get(AbstractKey<T> key) {
return key.getType().cast(map.get(key));
}
public <T> T put(AbstractKey<T> key, T value) {
return key.getType().cast(map.put(key, key.getType().cast(value)));
}
public static interface AbstractKey<K> {
Class<K> getType();
}
public static enum StringKey implements AbstractKey<String> {
A, B;
public Class<String> getType() {
return String.class;
}
}
public static enum IntegerKey implements AbstractKey<Integer> {
C, D;
public Class<Integer> getType() {
return Integer.class;
}
}
}
这会产生编译时错误
TypedMap map = new TypedMap();
TypedMap.AbstractKey<Integer> intKey = TypedMap.IntegerKey.C;
TypedMap.AbstractKey<String> strKey = TypedMap.StringKey.A;
map.put(strKey, "A");
map.put(intKey, 10);
map.put(strKey, 10); // this cause a compile error?