97

一段时间以来,我一直想知道在最佳实践中是否允许避免使用containsKey()on 方法java.util.Map,而是对来自get().

我的理由是,对值进行两次查找似乎是多余的——首先是 the containsKey(),然后是get()

另一方面,Map缓存最后一次查找的大多数标准实现可能是缓存,或者编译器可以以其他方式消除冗余,并且为了代码的可读性,最好保留该containsKey()部分。

非常感谢您的意见。

4

6 回答 6

119

某些 Map 实现允许具有空值,例如 HashMap,在这种情况下,如果get(key)返回null它不保证在映射中没有与此键关联的条目。

因此,如果您想知道地图是否包含密钥 use Map.containsKey。如果您只需要映射到键的值,请使用Map.get(key). 如果此映射允许 null 值,则返回值为 null 并不一定表示该映射不包含该键的映射;在这种情况下Map.containsKey是无用的并且会影响性​​能。此外,在并发访问地图的情况下(例如ConcurrentHashMap),在您测试之后Map.containsKey(key),该条目有可能在您调用之前被另一个线程删除Map.get(key)

于 2013-01-30T09:56:39.377 回答
45

我认为这是相当标准的写法:

Object value = map.get(key);
if (value != null) {
    //do something with value
}

代替

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

它的可读性并不低,效率也更高,所以我看不出有什么理由不这样做。显然,如果您的地图可以包含 null,则这两个选项的语义不同

于 2013-01-30T09:57:08.477 回答
8

正如 assylias 所指出的,这是一个语义问题。通常,Map.get(x) == null 是您想要的,但在某些情况下使用 containsKey 很重要。

一种这样的情况是缓存。我曾经在一个 Web 应用程序中处理性能问题,该应用程序经常查询其数据库以查找不存在的实体。当我研究该组件的缓存代码时,我意识到它正在查询数据库 if cache.get(key) == null。如果数据库返回 null(未找到实体),我们将缓存该键 -> null 映射。

切换到 containsKey 解决了这个问题,因为映射到空值实际上意味着什么。到 null 的键映射与不存在的键具有不同的语义。

于 2013-01-30T17:29:45.803 回答
5
  • containsKeyget仅当我们先验地知道永远不会允许空值时,后跟 a才是多余的。如果 null 值无效,则调用containsKey会带来不小的性能损失,并且只是开销,如下面的基准所示。

  • Java 8Optional习惯用法 -Optional.ofNullable(map.get(key)).ifPresentOptional.ofNullable(map.get(key)).ifPresent- 与普通的空检查相比会产生不小的开销。

  • AHashMap使用O(1)常量表查找,而 aTreeMap使用O(log(n))查找。当在containsKeya 上调​​用时,后跟get成语的速度要慢得多TreeMap

基准

https://github.com/vkarun/enum-reverse-lookup-table-jmh

// t1
static Type lookupTreeMapNotContainsKeyThrowGet(int t) {
  if (!lookupT.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupT.get(t);
}
// t2
static Type lookupTreeMapGetThrowIfNull(int t) {
  Type type = lookupT.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// t3
static Type lookupTreeMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupT.get(t)).orElseThrow(() -> new 
      IllegalStateException("Unknown Multihash type: " + t));
}
// h1
static Type lookupHashMapNotContainsKeyThrowGet(int t) {
  if (!lookupH.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupH.get(t);
}
// h2
static Type lookupHashMapGetThrowIfNull(int t) {
  Type type = lookupH.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// h3
static Type lookupHashMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupH.get(t)).orElseThrow(() -> new 
    IllegalStateException("Unknown Multihash type: " + t));
}
Benchmark (iterations) (lookupApproach) Mode Cnt Score Error Units

MultihashTypeLookupBenchmark.testLookup 1000 t1 avgt 9 33.438 ± 4.514 us/op
MultihashTypeLookupBenchmark.testLookup 1000 t2 avgt 9 26.986 ± 0.405 us/op
MultihashTypeLookupBenchmark.testLookup 1000 t3 avgt 9 39.259 ± 1.306 us/op
MultihashTypeLookupBenchmark.testLookup 1000 h1 avgt 9 18.954 ± 0.414 us/op
MultihashTypeLookupBenchmark.testLookup 1000 h2 avgt 9 15.486 ± 0.395 us/op
MultihashTypeLookupBenchmark.testLookup 1000 h3 avgt 9 16.780 ± 0.719 us/op

TreeMap 源参考

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java

HashMap 源码参考

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java

于 2018-10-06T17:08:49.683 回答
4

我们可以使用 Java8 Optional 使@assylias 的答案更具可读性,

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)
于 2016-06-24T14:42:43.513 回答
2

在 Java 中,如果您检查实现

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

两者都使用 getNode 来检索匹配项,主要工作在此完成。

冗余是上下文相关的,例如,如果您将字典存储在哈希映射中。当您想检索单词的含义时

正在做...

if(dictionary.containsKey(word)) {
   return dictionary.get(word);
}

是多余的。

但是如果你想根据字典检查一个单词是否有效。正在做...

 return dictionary.get(word) != null;

超过...

 return dictionary.containsKey(word);

是多余的。

如果您检查内部使用 HashMap 的HashSet实现,请在 'contains' 方法中使用 'containsKey'。

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
于 2018-06-04T12:32:25.790 回答