Java 具有多值类型的映射,具有泛型的优点

Java 具有多值类型的映射,具有泛型的优点,java,generics,map,casting,compiler-errors,Java,Generics,Map,Casting,Compiler Errors,我想创建一个映射,它将提供泛型的好处,同时支持多种不同类型的值。我认为以下是一般集合的两个主要优点: 将错误内容放入集合的编译时警告 从收藏中取出物品时无需强制转换 所以我想要的是一张地图: 它支持多个值对象 检查映射中的值(最好在编译时) 知道从地图获取时对象值是什么 使用泛型的基本情况是: Map<MyKey, Object> map = new HashMap<MyKey, Object>(); // No type checking on put(); m

我想创建一个映射,它将提供泛型的好处,同时支持多种不同类型的值。我认为以下是一般集合的两个主要优点:

  • 将错误内容放入集合的编译时警告
  • 从收藏中取出物品时无需强制转换
所以我想要的是一张地图:

  • 它支持多个值对象
  • 检查映射中的值(最好在编译时)
  • 知道从地图获取时对象值是什么
使用泛型的基本情况是:

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);
但是,如果我为键输入了错误的值,我不会得到任何编译错误。相反,我在get()上得到一个运行时
ClassCastException

如果this.put()能够给出一个编译错误,这将是理想的。 作为当前的第二个最佳选择,我可以在put()方法中抛出运行时
ClassCastException

所以,我想知道:

  • 为什么
    map.put(StringKey.A,10)
    不会导致编译错误
  • 如果值不是键的相关泛型类型,我如何调整此设计以在put时获得有意义的编译错误

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

  • 是否有其他设计可供我使用以实现我的目标

编辑-澄清:

  • 如果你认为这是一个糟糕的设计,你能解释为什么吗
  • 我使用了字符串和整数作为示例值类型——实际上,我希望能够使用多种不同的键/值类型对。我想在一张地图中使用这些,这就是目标

您正在以一种糟糕的方式处理泛型和重载。您正在扩展
HashMap
,因此您的类继承了方法
objectput(AbstractKey k,Object v)
。在类中,您正在定义另一个具有不同签名的
put
方法,这意味着您只是重载
put
方法,而不是重写它

编写
map.put(StringKey.A,10)
时,编译器会尝试找到符合参数类型
put(StringKey,Integer)
的方法。您的方法的签名不适用,但继承的
put
会--
StringKey
AbstractKey
兼容,
Integer
对象兼容。因此,它将该代码编译为对
HashMap.put
的调用

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


顺便说一句,从经验来看,你的方法很有趣,也很吸引人,但在现实生活中,这并不值得费心。

我知道,每个问题都源于原始设计的味道:想要把不同类型的值放到地图上。我将把整数和字符串值包装成一个通用的
类型。大概是这样的:

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
}
这样,您只需要一个
映射
,就可以安全地从映射中获取值:

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

项目29:考虑类型化异构容器。-(

< P>):(由于有效java)

公共类类型映射{
私有映射,对象>();
公共T获取(抽象密钥){
return key.getType().cast(map.get(key));
}
公共T put(抽象键,T值){
return key.getType().cast(map.put(key,key.getType().cast(value));
}
公共静态接口抽象密钥{
类getType();
}
公共静态枚举StringKey实现AbstractKey{
A、 B;
公共类getType(){
返回字符串.class;
}
}
公共静态enum IntegerKey实现AbstractKey{
C、 D;
公共类getType(){
返回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?
TypedMap map=newtypedmap();
TypedMap.AbstractKey intKey=TypedMap.IntegerKey.C;
TypedMap.AbstractKey strKey=TypedMap.StringKey.A;
地图放置(strKey,“A”);
地图放置(intKey,10);
地图放置(斯特基,10);//这会导致编译错误吗?

您将其描述为“原始设计气味”——您能解释一下您认为设计有什么问题吗?将不同类型的值放在地图中意味着您必须在值上使用instanceof来处理这些值。一种更面向对象的方法是为映射中所有可能的值提供一个公共接口或超类,并使用此公共接口或超类作为映射的值类型。如果这是不可能的,这可能意味着你应该使用几个地图。而且-我不认为这个解决方案真的有帮助。您所做的只是找到一种从映射中确定值类型的替代方法。我甚至建议,这不如我的方法,因为你必须知道a值的类型,这样你就可以对它进行转换。我看不出这能确保映射时的类型安全。但我不必做instanceof,因为我得到的值的对象类型是由AbstractKey的泛型决定的-这是我设计的全部要点。它提高了类型安全性,因为唯一可能放入映射的东西是值实例。这些只能包装一个字符串或整数。在你的问题中,你从一张地图开始,它可以包含任何类型的对象。也许您应该在更高的层次上向我们解释为什么需要将整型值和字符串值存储到唯一的映射中。如果有两个映射(一个用于整数值,另一个用于字符串值),则不必创建任何特定的类。否-我的put()方法的返回值是K。这符合继承的方法public V put(K键,V值)。我将
put
Set.add
相混淆。但要点是
map.put(StringKey.A, 10); // why doesn't this cause a compile error?
String a = map.get(StringKey.A); // throws a 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);
}
map.put(StringKey.A, 10); // now throws a ClassCastException
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
}
Value v = map.get(someKey);
if (v.getType() == Type.STRING) {
    String s = v.getStringValue();
}
else if (v.getType() == Type.INTEGER) {
    Integer i = v.getIntegerValue();
}
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?