java.util.Map
上的containsKey()
方法,而是对get()
的结果进行空值检查。我的理由是,这样做似乎重复了两次查找值--首先是
containsKey()
,然后是get()
。另一方面,可能大多数标准的
Map
实现会缓存最后一次查找或者编译器可以消除冗余。为了代码可读性,保留containsKey()
部分可能更好。非常感谢您的意见。
java.util.Map
上的containsKey()
方法,而是对get()
的结果进行空值检查。containsKey()
,然后是get()
。Map
实现会缓存最后一次查找或者编译器可以消除冗余。为了代码可读性,保留containsKey()
部分可能更好。一些 Map 实现允许有空值存在,例如 HashMap,此时如果 get(key)
返回 null
,并不保证这个 key 没有与之关联的 entry。
因此,如果你想知道一个 Map 是否包含某个 key,请使用 Map.containsKey
。如果你只需要获取 key 对应的 value,请使用 Map.get(key)
。如果这个 Map 允许 null 值存在,那么返回 null 并不一定表示 Map 中不存在该 key 的映射;在这种情况下,Map.containsKey
是没用的,并且会影响性能。此外,在对 Map 进行并发访问的情况下(例如 ConcurrentHashMap),在测试完 Map.containsKey(key)
后,有可能在调用 Map.get(key)
之前另一个线程已经将 entry 删除了。
我认为写成以下形式是相当标准的:
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,则这两个选项的语义不同。
如果我们事先知道空值将不会被允许,那么使用containsKey
后跟get
是多余的。如果空值无效,调用containsKey
将带来非常显著的性能损失,并且只是开销,如下面的基准测试所示。
与仅使用普通的null检查相比,Java 8 Optional
惯用法 - Optional.ofNullable(map.get(key)).ifPresent
或Optional.ofNullable(map.get(key)).ifPresent
- 需要承担非常显著的开销。
HashMap
使用 O(1)
的常数表查找,而 TreeMap
使用O(log(n))
的查找。在TreeMap
上调用containsKey
并跟随get
惯用法时,速度慢得多。
请参见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));
}
基准测试 (迭代次数) (查找方法) 模式 Cnt 分数 误差 单位
MultihashTypeLookupBenchmark.testLookup 1000 t1 avgt 9 33.438 ± 4.514 微秒/操作 MultihashTypeLookupBenchmark.testLookup 1000 t2 avgt 9 26.986 ± 0.405 微秒/操作 MultihashTypeLookupBenchmark.testLookup 1000 t3 avgt 9 39.259 ± 1.306 微秒/操作 MultihashTypeLookupBenchmark.testLookup 1000 h1 avgt 9 18.954 ± 0.414 微秒/操作 MultihashTypeLookupBenchmark.testLookup 1000 h2 avgt 9 15.486 ± 0.395 微秒/操作 MultihashTypeLookupBenchmark.testLookup 1000 h3 avgt 9 16.780 ± 0.719 微秒/操作
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
Optional.ofNullable(map.get(key)).ifPresent(value -> {
//do something with value
};)
如果你查看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);
是多余的。
如果您查看HashSet实现,它在内部使用HashMap,使用'containsKey'方法来实现'contains'方法。
public boolean contains(Object o) {
return map.containsKey(o);
}
null
,你是否想要将其与未设置的键/值区别对待?如果您不需要特别区分它们,可以直接使用get()
。 - Peter LawreyMap
是private
的,你的类可能能够保证在map中永远不会插入null
。在这种情况下,你可以使用get()
并检查null
是否存在,而不是使用containsKey()
。在某些情况下,这样做可能更清晰、更高效。 - Raedwald