使用具有generics优势的多种值类型映射

我想创建一个地图,它将提供generics的好处,同时支持多种不同类型的值。 我认为以下是通用集合的两个关键优势:

  • 编写时间警告将错误的东西放入集合中
  • 从集合中获取东西时无需进行强制转换

所以我想要的是一张地图:

  • 它支持多个值对象,
  • 检查放入地图的值(最好是在编译时)
  • 知道从地图获取时的对象值。

使用generics的基本案例是:

Map map = new HashMap(); // 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 { } public enum StringKey implements AbstractKey{ A,B; } public enum IntegerKey implements AbstractKey{ C,D; } 

然后我可以创建一个TypedMap,并覆盖put()和get()方法:

 public class TypedMap extends HashMap { public  K put(AbstractKey key, K value) { return (K) super.put(key, value); } public  K get(AbstractKey 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 put(AbstractKey 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上获得有意义的编译错误,其中值不是关键的通用类型的键?

  • 这是一个合适的设计来实现我想要的(见上)? (任何其他想法/意见/警告也将不胜感激……)

  • 有没有我可以用来实现我想要的替代设计?

编辑 – 澄清:

  • 如果你认为这是一个糟糕的设计 – 你能解释一下原因吗?
  • 我使用String和Integer作为示例值类型 – 实际上我有许多不同的Key / value类型对,我希望能够使用它们。 我想在一张地图中使用这些 – 这就是目标。

你正在搞乱generics和过载。 您正在扩展HashMap ,因此您的类inheritance了Object put(AbstractKey k, Object v) 。 在您的类中,您正在定义另一个具有不同签名的put方法,这意味着您只是重载put方法,而不是覆盖它。

当您编写map.put(StringKey.A, 10) ,编译器会尝试查找符合put(StringKey, Integer)参数类型的方法。 您的方法的签名不适用,但inheritance的put的确是 – StringKeyAbstractKey兼容, IntegerObject兼容。 因此它将该代码编译为对HashMap.put的调用。

解决此问题的一种方法:将put重命名为某个自定义名称,例如typedPut

BTW从经验谈起你的方法非常有趣和吸引人,但在现实生活中它不值得麻烦。

第29项:考虑类型安全的异构容器。 – Joshua Bloch, Effective Java,第二版,第5章:generics

恕我直言,每个问题都来自原始的设计气味:想要将不同类型的值放入地图中。 我会将您的Integer和String值换行为常用的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 ,就可以安全地从地图中获取值:

 Value v = map.get(someKey); if (v.getType() == Type.STRING) { String s = v.getStringValue(); } else if (v.getType() == Type.INTEGER) { Integer i = v.getIntegerValue(); } 

考虑一下:(感谢有效的java)

 public class TypedMap { private Map, Object> map = new HashMap, Object>(); public  T get(AbstractKey key) { return key.getType().cast(map.get(key)); } public  T put(AbstractKey key, T value) { return key.getType().cast(map.put(key, key.getType().cast(value))); } public static interface AbstractKey { Class getType(); } public static enum StringKey implements AbstractKey { A, B; public Class getType() { return String.class; } } public static enum IntegerKey implements AbstractKey { C, D; public Class getType() { return Integer.class; } } } 

这会生成编译时错误

 TypedMap map = new TypedMap(); TypedMap.AbstractKey intKey = TypedMap.IntegerKey.C; TypedMap.AbstractKey strKey = TypedMap.StringKey.A; map.put(strKey, "A"); map.put(intKey, 10); map.put(strKey, 10); // this cause a compile error?