我一直想知道在最佳做法中是否允许在containsKey()
上使用java.util.Map
方法,而是对get()
的结果进行空检查。
我的理由是,对值进行两次查找似乎是多余的 - 首先是containsKey()
,然后是get()
。
另一方面,Map
的大多数标准实现可能会缓存最后一次查找,或者编译器可以以其他方式取消冗余,并且为了代码的可读性,最好保持{ {1}}部分。
我非常感谢你的评论。
答案 0 :(得分:103)
允许某些Map实现具有空值,例如HashMap,在这种情况下,如果get(key)
返回null
,则不保证在与该键关联的映射中没有条目。
因此,如果您想知道地图是否包含密钥,请使用Map.containsKey
。如果您只需要映射到键的值,请使用Map.get(key)
。如果此映射允许空值,则返回值null不一定表示映射不包含该键的映射;在这种情况下,Map.containsKey
是无用的,会影响性能。此外,在并发访问地图的情况下(例如ConcurrentHashMap
),在您测试Map.containsKey(key)
之后,在您调用Map.get(key)
之前,该条目可能会被另一个线程删除。< / p>
答案 1 :(得分:39)
我认为写作是相当标准的:
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,则这两个选项不具有相同的语义。
答案 2 :(得分:6)
正如assylias指出的那样,这是一个语义问题。通常,Map.get(x)== null是您想要的,但有些情况下使用containsKey很重要。
一个这样的案例是缓存。我曾经在Web应用程序中处理性能问题,该应用程序经常查询其数据库以查找不存在的实体。当我研究该组件的缓存代码时,我意识到如果cache.get(key)== null则查询数据库。如果数据库返回null(未找到实体),我们将缓存该密钥 - &gt;空映射。
切换到containsKey解决了这个问题,因为映射到空值实际上意味着什么。键映射到null具有与不存在的键不同的语义含义。
答案 3 :(得分:2)
我们可以使用Java8 Optional,
使@assylias回答更具可读性Optional.ofNullable(map.get(key)).ifPresent(value -> {
//do something with value
};)
答案 4 :(得分:1)
在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);
}
答案 5 :(得分:1)
containsKey
后跟get
才是多余的。如果空值无效,则containsKey
的调用会带来不小的性能损失,并且只是开销,如下面的基准所示。
Java 8 Optional
习惯用法-Optional.ofNullable(map.get(key)).ifPresent
或Optional.ofNullable(map.get(key)).ifPresent
-与普通null检查相比,产生了不小的开销。
HashMap
使用O(1)
常量表查找,而TreeMap
使用O(log(n))
查找。在containsKey
上调用时,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
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java