在Java中,在Map中使用Set作为键时应该采取什么预防措施?

时间:2011-08-10 07:36:23

标签: java map set

我不确定在地图中使用动态对象(如集合作为键)的主流观点。

我知道典型的Map实现(例如,HashMap)使用哈希码来决定将条目放入哪个桶,如果该哈希码应该以某种方式改变(可能因为Set的内容应该发生变化,那么通过导致错误地计算存储桶(与最初将Set插入HashMap的方式相比),可能会弄乱HashMap。

但是,如果我确保Set内容根本没有变化,那么这是否可行?即便如此,这种方法通常被认为是容易出错的,因为集合具有固有的易变性(即使采取了预防措施以确保它们不被修改)?

看起来Java允许将函数参数指定为final;这可能是一个可以采取的一个小小的预防措施?

人们甚至在商业/开源实践中做这样的事情吗? (将List,Set,Map等作为键放在地图中?)

我想我应该描述一下我正在努力实现的目标,这样才能使动机变得更加清晰,也许可以提出替代实施方案。

我想要完成的是拥有这样的东西:

class TaggedMap<T, V> {
  Map<Set<T>, V> _map;
  Map<T, Set<Set<T>>> _keys;
}

...本质上,能够用某些键(T)“标记”某些数据(V)并写入其他辅助功能来访问/修改数据并用它做其他奇特的东西(即返回一个列表)满足一些密钥标准的所有条目)。 _keys的功能是作为一种索引,以便于查找值而无需遍历所有_map的条目。

在我的情况下,我打算专门使用T = String,V = Integer。我与之交谈的人建议用字符串代替Set,即:

class TaggedMap<V> {
  Map<String, V> _map;
  Map<T, Set<String>> _keys;
}

其中_map中的键的类型为“key1; key2; key3”,其中键由分隔符分隔。但我想知道我是否可以完成一个更通用的版本,而不必在密钥之间强制执行带分隔符的字符串。

我想知道的另一件事是,是否有某种方法可以将其作为Map扩展。我想象的是:

class TaggedMap<Set<T>, V> implements Map<Set<T>, V> {
  Map<Set<T>, V> _map;
  Map<T, Set<Set<T>>> _keys;
}

然而,我无法将其编译,可能是由于我对泛型的理解较差。以此作为目标,任何人都可以修复上述声明,以便它根据我所描述的精神工作或建议一些轻微的结构修改?特别是,我想知道“实施地图,V&gt;”是否可以声明这样一个复杂的接口实现。

3 个答案:

答案 0 :(得分:9)

如果你确定

,那是对的
  1. Set内容未被修改,
  2. Set本身未被修改
  3. 将它们用作Map中的键是完全安全的。

    很难确保(1)不会意外违反。一种选择可能是专门设计存储在Set内的类,以便该类的所有实例都是不可变的。这可以防止任何人意外更改其中一个Set键,因此(1)将无法实现。例如,如果您使用Set<String>作为密钥,则无需担心由于外部修改而导致String内的Set更改。

    您可以使用Collections.unmodifiableSet方法轻松地使(2)成为可能,该方法返回无法修改的Set的包装视图。这可以对任何Set进行,这意味着对你的密钥使用这样的东西可能是一个非常好的主意。

    希望这有帮助!如果您的用户名意味着我的想法,那么学习每种语言都会好运! : - )

答案 1 :(得分:0)

正如您所提到的,集合可以更改,即使您阻止集合更改(即它包含的元素),元素本身也可能会发生变化。那些因素进入哈希码。

你能用更高层次的术语描述你想要做什么吗?

答案 2 :(得分:0)

@ templatetypedef的答案基本上是正确的。如果集合的状态在密钥期间无法更改,则只能在某些数据结构中安全地使用Set作为密钥。如果集合的状态发生变化,则会违反数据结构的不变量,对其进行操作会得到错误的结果。

使用Collections.unmodifiableSet创建的包装器可以提供帮助,但有一个隐藏的问题。如果原始集仍然可以直接访问,则应用程序可以对其进行修改; e.g。

public void addToMap(Set key, Object value);
    someMap.put(Collections.unmodifiableSet(key), value);
}

// but ...

Set someKey = ...
addToMap(someKey, "Hi mum");
...
someKey.add("something");  // Ooops ... 

为了保证不会发生这种情况,您需要在包装之前对该集进行深层复制。这可能很昂贵。


使用Set作为关键字的另一个问题是它可能很昂贵。实现键/值映射有两种通用方法;使用hashcode方法或使用实现排序的compareTo方法。这两套都很贵。