【问题标题】:Java - map 2 keys of different types to the same value (any type). How can I go about it?Java - 将 2 个不同类型的键映射到相同的值(任何类型)。我该怎么办?
【发布时间】:2013-10-16 11:01:49
【问题描述】:

好的,这就是场景。我有 静态总是在一起的键。一个键是索引(又名int),一个键是描述(又名stringenum)。我提前知道所有这些键,所以唯一真正改变的是值,永远不会添加新键。

然而,这些值可以是任何类型:string、int、long。有些值不是单数,而是由多个值组成。但是,我事先确实知道每个密钥对将指向哪种类型的值。

这些值很可能总是使用索引来设置。但是,我希望能够通过 index(int) 或描述 (string/枚举)。此外,通过索引访问值时,我还应该可以访问描述

这可能会让事情更清楚:

1/Name ----> "danny"        //1 and Name are known in advance and always go together. also, they always point to a string
2/Age  ----> 24             //2 and Age are known in advance and always go together. also, they always point to an int
3/Time ----> 352343463463L  //3 and Time are known in advance and always go together. also, they always point to a long
4/Occupation    ---> [description] "magician"
                ---> [type] "entertainer"
                ---> [years] 3              //4 and Status are known in advance and always go together. also they will always point to 2 strings and and an int (or an object contraining 2 strings and an int...)

需要的功能:

set(1, "Jasmine");
get(1);             //returns "Jasmine"
get(Name);          //return "Jasmine"  (name can be either string or enum I suppose)
getDescription(1);  // returns Name (again, name could be either string or enum). this function could possibly be merged with get(1) to have it return both description and value in the first place.

set(2, 32);
get(2);             //returns 32
get(Age);            //returns 32

【问题讨论】:

  • 请澄清您所说的“相同不同”到底是什么意思。
  • 你知道,你所描述的基本上是一个具有属性的类......
  • 这两个键总是指向同一个值。然而,该值可以是任何类型。因此,键指向相同的不同类型值。这是一个话题,所以我尽量缩短:)
  • 不可能创建一个方法get(int),它会根据 int 的值返回不同的编译时类型。所以忘记不必按索引进行查找。

标签: java map key


【解决方案1】:

更新 这是我的明确答案:

您在索引、描述和与之关联的值类型之间存在静态关系。所以让我们在一个类中捕获它:

public class Key<VALUETYPE> {
    private final Integer          index;
    private final String           description;
    private final Class<VALUETYPE> valueType;

    public Key(final Integer index, final String description, final Class<VALUETYPE> valueType) {
        super();
        this.index = index;
        this.description = description;
        this.valueType = valueType;
    }

    public Integer getIndex() {
        return index;
    }

    public String getDescription() {
        return description;
    }

    public Class<VALUETYPE> getValueType() {
        return valueType;
    }

    @Override
    public int hashCode() {
        return index.hashCode();
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj) { return true; }
        if (obj == null) { return false; }
        if (getClass() != obj.getClass()) { return false; }
        Key<?> other = (Key<?>) obj;
        return index.equals(other.index);
    }
}

我假设索引是键的唯一标识符,所以基于它的hashCodeequals

现在您编写一个涵盖您的用例的访问器类:

public class MapAccessor {
    private final Map<Integer, Key<?>> keyMap;
    private final Map<Key<?>, Object>  valueMap;

    public MapAccessor(final Map<Integer, Key<?>> keysByIndex, final Map<Key<?>, Object> valueMap) {
        this.keyMap = keysByIndex;
        this.valueMap = valueMap;
    }

    public void put(final Integer index, final Object value) {
        Key<?> key = keyMap.get(index);
        if (key.getValueType().isInstance(value) || value == null) {
            valueMap.put(key, value);
        }
        else {
            throw new IllegalArgumentException("Wrong type of value for index " + index + ", expected: " + key.getValueType()
                    + ", actual: " + value.getClass());
        }
    }

    public <VALUETYPE> VALUETYPE get(final Key<VALUETYPE> key) {
        return key.getValueType().cast(valueMap.get(key));
    }

    public Object get(final Integer index) {
        Key<?> key = getKey(index);
        return key == null ? null : get(key);
    }

    public Key<?> getKey(final Integer index) {
        return keyMap.get(index);
    }

    public String getDescription(final Integer index) {
        Key<?> key = getKey(index);
        return key == null ? null : key.getDescription();
    }
}

或者,您可以将其放在 HashMap&lt;Key&lt;?&gt;, Object&gt; 的子类中,而不是委托给它。

让我们演示如何使用上述内容。请注意,对于按索引查找,强制转换是不可避免的。

public class ExampleUsage {
    private static final Key<String>   NAME  = new Key<>(1, "Name", String.class);
    private static final Key<Integer>  AGE   = new Key<>(2, "Age", Integer.class);

    private static final Map<Integer, Key<?>> keysByIndex = buildKeysByIndex(NAME, AGE);

    public static void main(final String... args) {
        Map<Key<?>, Object> valueMap = new HashMap<>();

        MapAccessor accessor = new MapAccessor(keysByIndex, valueMap);

        accessor.put(1, "Jasmine");
        String nameByIndex = (String) accessor.get(1); // returns "Jasmine", cast can't be avoided
        String nameByKey = accessor.get(NAME); // returns "Jasmine", no cast necessary
        Key<?> nameKeyByIndex = accessor.getKey(1); // returns NAME
        String nameDescriptionByIndex = accessor.getDescription(1); // returns "Name"

        accessor.put(2, 32);
        Integer ageByIndex = (Integer) accessor.get(2); // returns 32, cast can't be avoided
        Integer ageByKey = accessor.get(AGE); // returns 32, no cast necessary
        Key<?> ageKeyByIndex = accessor.getKey(2); // returns AGE
        String ageDescriptionByIndex = accessor.getDescription(2); // returns "Age"
    }

    private static Map<Integer, Key<?>> buildKeysByIndex(final Key<?>... keys) {
        Map<Integer, Key<?>> keyMap = new HashMap<Integer, Key<?>>();
        for (Key<?> key : keys) {
            keyMap.put(key.getIndex(), key);
        }
        return Collections.unmodifiableMap(keyMap);
    }
}

【讨论】:

  • 我非常感谢您的解决方案的深度。我仍在尝试解决这个问题,因为它涉及一些我不熟悉的 Java 程序,现在正在阅读(我大约一周前才开始使用)。在我完全了解这一点之前,我只需要提前问一件事:你提供了 4 个吸气剂,一个显式地使用了演员表。使用其他 3 个 getter 时是否进行了任何转换,或者它们会像任何简单的 map getter 一样快吗?
  • 使用 Key 的 getter(MapAccessor 中的其他 2 个 getter 使用)执行动态转换,以便能够快速失败,以防值映射在访问器使用了错误类型的值。这对性能影响不大,但您可以将其替换为静态(编译时)强制转换为 (VALUETYPE),该类型在编译后(由于擦除)被删除,如果您愿意,可以忽略安全警告。只要仅使用 MapAccessor 类来操作地图,值的类型就会正确。
【解决方案2】:

创建一个简单的类来保存数据,并创建 NameAge 等带有支持字段的类 bean 属性:

private int age; 

public getAge() { return age; } 

public setAge(int value) { age = value; }

创建一个名为 @FieldInfo 的自定义注释,其中包含一个字段序号和一个字段描述:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)    
public @interface FieldInfo {
    int ordinal();
    String description();
}

用你的注解装饰数据类:

@FieldInfo(ord=1, description="Age")
private int age; 

public getAge() { return age; } 

public setAge(int value) { age = value; }

为您的数据类创建一个基类,其中包含通用的get()set() 方法,这些方法可以使用字段序号和 getDescription() 方法获取/设置值,该方法返回给定序号的字段描述。使用this.getClass().getDeclaredFields()通过反射获取Fields[],然后使用Field.getAnnotation(FieldInfo.class)获取每个字段的注解。

由于数据类属性在运行时不会更改,因此如果您经常使用序数访问字段,您可以在静态构造函数中为每个数据类类型构建两个静态查找 &lt;int, Field&gt;&lt;int, String&gt;,以加快处理速度。使用这种方法,您可以通过扩展注释来进一步描述字段,并且您的数据类仍然是具有传统 getter/setter 的简单类。

【讨论】:

    【解决方案3】:

    您可以使用我的TypedMap。简而言之,它为您提供了一个类型安全的映射,可以将任何类型的对象存储为键下的值:

    TypedMap map = new TypedMap();
    
    String expected = "Hallo";
    map.set( KEY1, expected );
    String value = map.get( KEY1 ); // Look Ma, no cast!
    assertEquals( expected, value );
    
    List<String> list = new ArrayList<String> ();
    map.set( KEY2, list );
    List<String> valueList = map.get( KEY2 ); // Even with generics
    assertEquals( list, valueList );
    

    为了让您快速访问键入的键,我建议使用枚举:

    enum Key {
        Name(1) { @Override public TypedMapKey<String> getKey() { return NAME_KEY; },
        ...;
    
        private static Key[] byIndex = new Key[MAX_INDEX+1];
        static {
            for( Key key : values() ) { byIndex[key.index] = key; }
        }
    
        public static byIndex(int index) {
            return byIndex[index]; // I suggest non-null checks here if you have gaps
        }
    
        private Key(int index) {
            this.index = index;
        }
    
        public TypedMapKey<?> getKey() { throw new UnsupportedOperationException( "Please override"; ) }
    }
    

    【讨论】:

    • 我认为这里的魔力在于未显示的KEY1(或KEY2)声明,即映射键是类型信息的载体。
    • 我查看了您的 TypedMap,但它似乎有缺陷:转换发生在 TypedMap 本身中,并且仍然可以在地图中放置错误类型的对象。
    • @herman:那是因为它还实现了Map 接口。如果您想真正省钱,请让put( String key, Object value ) 抛出UnsupportedOperationException。这样,投射总是安全的,因为您不能再将错误的对象放入地图中。或者,您可以删除 Map 接口,但它不再是 Java 映射的直接替代品。
    • @BorisB.:是的。整个代码在链接后面加上解释。
    • 如果你可以使用自定义键,你有不同的选择来绕过演员表,但他想使用一个简单的索引(Integer)和一个enum(或String)作为键.
    【解决方案4】:

    创建

    class Entry {
      int index;
      String description;
      Object value;
    }
    

    声明2个HashMap:

    HashMap<Integer, Entry> idxValue=new HashMap<Integer, Entry>();
    HashMap<String, Entry> descrValue=new HashMap<String, Entry>();
    

    定义方法来存储和检索适用于两个表的条​​目和值。

    【讨论】:

    • 他如何使用它来设置仅使用索引的值,然后使用描述取回?
    • @herman idxValue.get(index).setValue((Object) othervalue), descrValue.get(description) 现在将指向带有othervalue 的新更改对象。 +1 Hashmap 是要走的路
    • @herman 是的,这是有问题的。存储值时,应提供索引和描述。所以问题中的例子是不一致的。
    • @reverse_engineer 是的,如果 Entry 是可变的,但我不确定这是个好主意。
    • @AlexeiKaigorodov 查看我的答案:存储值时不需要索引和描述。根据问题,索引和描述之间的映射是静态的。
    猜你喜欢
    • 2018-02-28
    • 2013-08-11
    • 2014-03-31
    • 2018-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-11
    • 1970-01-01
    相关资源
    最近更新 更多