【问题标题】:In Java, what precautions should be taken when using a Set as a key in a Map?在Java中,在Map中使用Set作为key时应该注意什么?
【发布时间】:2011-10-23 20:31:07
【问题描述】:

我不确定在 Maps 中使用动态对象(例如 Set)作为键的主流观点是什么。

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

但是,如果我确保 Set 内容根本不改变,这是否是一个可行的选择?即便如此,由于 Set 固有的易变性质,这种方法是否通常被认为容易出错(即使采取了预防措施以确保它们不被修改)?

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

人们甚至在商业/开源实践中做这样的事情吗? (把 List、Set、Map 等作为 Maps 中的键?)

我想我应该描述一下我想用这个来完成什么,这样动机就会变得更加清晰,也许可以建议替代实现。

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

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”类型,键由分隔符分隔。但我想知道我是否可以完成一个更通用的版本,而不必强制在键之间使用分隔符的字符串。

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

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

但是,我无法编译它,这可能是由于我对泛型的理解不足。以此为目标,任何人都可以修复上述声明,使其按照我所描述的精神工作,或建议一些轻微的结构修改吗?特别想知道“implements Map, V>”子句,是否可以声明这么复杂的接口实现。

【问题讨论】:

  • 如果您将类型更改为:class TaggedMap&lt;T, V&gt; implements Map&lt;Set&lt;T&gt;, V&gt;,您的最后一个 sn-p 就可以了。

标签: java map set


【解决方案1】:

@templatetypedef 的回答基本正确。如果集合的状态在它是键时不能更改,您只能安全地使用 Set 作为某些数据结构中的键。如果集合的状态发生变化,就会违反数据结构的不变量,对其进行操作会得到错误的结果。

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

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 方法。这两个对于套装来说都是昂贵的。

【讨论】:

    【解决方案2】:

    正如你提到的,集合可以改变,即使你阻止集合改变(即它包含的元素),元素本身也可能改变。这些因素会影响哈希码。

    你能用更高级的术语描述你正在尝试做什么吗?

    【讨论】:

      【解决方案3】:

      如果你确保你是正确的

      1. Set 的内容没有被修改,并且
      2. Sets 本身没有被修改

      将它们用作Map 中的键是完全安全的。

      很难确保 (1) 不会被意外违反。一种选择可能是专门设计存储在Set 中的类,以便该类的所有实例都是不可变的。这将防止任何人意外更改 Set 键之一,因此 (1) 是不可能的。比如你使用Set&lt;String&gt;作为key,就不用担心Set里面的Strings会因为外部修改而改变。

      您可以使用Collections.unmodifiableSet 方法很容易地使(2)成为可能,该方法返回无法修改的Set 的包装视图。可以对任何Set 执行此操作,这意味着为您的密钥使用类似的内容可能是一个非常好的主意。

      希望这会有所帮助!如果你的用户名和我想的一样,祝你好运学习每一种语言! :-)

      【讨论】:

      • Collections.unmodifiableSet 返回 Set 的不可修改视图,但内容仍可能更改。如果任何代码仍然有对原始集合的引用,则可以修改集合内容。因此,为了真正安全,您需要使用 Guava 的 ImmutableSet 类。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-08
      • 2015-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-24
      相关资源
      最近更新 更多