我什么时候应该为Map键创建一个类?

时间:2010-12-08 13:56:45

标签: java map

我正在使用Java 6.

假设我有一个类,我想将其实例保存到地图中。稍后我想仅使用“关键字段”检索实例。我将忽略字段修饰符,getter和setter以简洁。

class A {

    String field1;
    String field2;
    String field3;
    String field4;
    //more fields

    public int hashCode(){
        //uses only field1 and field2
    }

    public boolean equals(Object o){
        //uses only field1 and field2
    }
}

由于Java的标准API没有MultikeyMap,我不想使用第三方库,我可以选择
1)创建一个新的类KeyA来表示地图的关键字 2)当我需要从地图中检索对象时,使用A本身作为键并仅填充“关键字段” 3)嵌套地图,例如HashMap<String, HashMap<String, A>>
4)其他解决方法

人们通常使用什么以及何时使用?

3 个答案:

答案 0 :(得分:2)

鉴于您最近的编辑,在这种情况下,您应该可以使用A类实例作为键。查找将基于equals()hashCode()的语义完成,因此这将导致仅通过“关键字段”检索实例。因此,以下代码可以按预期工作:

final Map<A, String> map = new HashMap<A, Object>();
final A first = new A("fe", "fi", "fo", "fum");

map.put(first, "success");

// later on
final A second = new A ("fe", "fi", "foo", "bar");
System.out.println(map.get(second)); // prints "success";

话虽如此,您对选项2的描述让我有点担心这可能不是最明智的选择。如果您创建Map<A, String>,那就是从类A 的实例到字符串的映射。然而,您的第二点暗示您希望将其视为从对关键字段到字符串的映射。如果您通常会根据一些“原始”字符串查找值,那么我建议不要这样做。对我来说,创建A的“假”实例只是为了进行查找是错误的 - 所以在这种情况下,你可能应该创建一个包含选项1中描述的字符串对的键类。(你可以甚至在你的A对象中嵌入这些实例来保存关键字段。

对选项3也有类似的论据。如果字符串确实在概念上是分层的,那么它可能很有意义。例如,如果field1是国家/地区,而field2是城镇,则可以肯定地说嵌套地图是有意义的 - 您有从国家/地图到城镇地图的映射 - &gt;内部关系那个国家。但是如果你的密钥不以这种方式自然地组成(比如,如果它们是(x,y)坐标),那么这也不是表示数据的非常自然的方式,而是来自{{1的单级映射价值会更明智。 (同样地,如果你从不使用两级地图,除了总是直接穿过两个层,人们可能会认为单层地图仍然更有意义。)

最后,对于选项4 - 如果你总是映射到XYPoint本身,并将密钥存储为自己的值(例如,如果你想规范你的{{ 1}}实例,有点像A)然后根据指出你根本不需要使用A,而String.intern()将在这里完成工作。当您想要在不同的对象之间建立关系时,Map非常有用,而Set会自动为您提供对象的唯一性,而不会产生任何额外的概念开销。


如果您确实使用类本身作为键,请注意,如果对象Map(以及Set的行为)不会随时间变化,通常只能用作键。 。通常这意味着密钥是不可变的,但在这里你可以负担得到可变的“非密钥”字段。如果你打破这个规则,你会看到奇怪的行为,如下所示:

hashCode

答案 1 :(得分:0)

我创建了一个Index类,类似这样(警告:未经测试的代码),以抽象出索引功能。为什么Java没有这样的东西已经令我感到困惑。

interface Indexer<T, K> 
{
    /** extract key from index */
    public K getIndexKey(T object); 
}

class Index<T,K>
{
    final private HashMap<K,List<T>> indexMap = new HashMap<K,List<T>>();
    final private Indexer<T,K> indexer;

    public Index(Indexer<T,K> indexer)
    {
        this.indexer = indexer;
    }

    public void add(T object) {
        K key = this.indexer.getIndexKey(object);
        List<T> values = this.indexMap.get(key);
        if (values == null)
        {
            values = new ArrayList<T>();
            this.indexMap.put(key, values);
        }
        values.add(object);
    }
    public void remove(T object) {
        K key = this.indexer.getIndexKey(object);
        List<T> values = this.indexMap.get(key);
        if (values != null)
        {
            values.remove(object);
        }
    }
    public List<T> lookup(K key) {
        List<T> values = this.indexMap.get(key);
        return values == null 
           ? Collections.emptyList()
           : Collections.unmodifiableList(values);
    }
}

与您的class A相关的示例:

Index<A,String> index1 = new Index<A,String>(new Indexer<A,String>() {
    @Override public String getIndexKey(A object)
    {
        return object.field1;
    }
});    
Index<A,String> index2 = new Index<A,String>(new Indexer<A,String>() {
    @Override public String getIndexKey(A object)
    {
        return object.field2;
    }
});    
/* repeat for all desired fields */

您需要手动添加和删除索引中的条目,但这些操作下面的所有垃圾工作都由Index类处理。

答案 2 :(得分:0)

您的班级有“关键字段”。我建议使用这些关键字段(当然映射到您域中的概念)创建父类ParentA,并在子类A中继承此类。

覆盖hashCode()类中的equals()ParentA

使用Map<ParentA, A>存储您的A个实例,并将该实例作为键和值。

要检索特定的A实例,请设置新的ParentA实例pA,并设置关键字段,然后执行

A a = map.get(pA);

就是这样。

另一种方法是使用关键字段创建AIdentifier类,并将实例添加为A属性id。因此,您使用map.put(a.id, a);添加实例,这是继承与组合模式讨论:)