しばらくの間、Java.util.Map
でcontainsKey()
メソッドの使用を控え、代わりにget()
の結果に対してnullチェックを行うのがベストプラクティス内で許容されるかどうか疑問に思っていました。
私の理論的根拠は、最初にcontainsKey()
に対して、次にget()
に対して値のルックアップを2回行うのは冗長に思えるということです。
一方、Map
のほとんどの標準実装は最後のルックアップをキャッシュするか、コンパイラが冗長性を排除できる可能性があり、コードの可読性のためにcontainsKey()
パート。
コメントをお願いします。
一部のMap実装では、HashMapなどのnull値を使用できます。この場合、get(key)
がnull
を返す場合、このキーに関連付けられたマップにエントリがないことを保証しません。
したがって、マップにキーが含まれているかどうかを知りたい場合は、Map.containsKey
を使用します。キーにマップされた値のみが必要な場合は、Map.get(key)
を使用します。このマップがnull値を許可する場合、nullの戻り値は、マップにキーのマッピングが含まれていないことを必ずしも示しません。そのような場合、Map.containsKey
は役に立たず、パフォーマンスに影響します。また、マップへの同時アクセス(例:ConcurrentHashMap
)の場合、Map.containsKey(key)
をテストした後、Map.get(key)
を呼び出す前に別のスレッドによってエントリが削除される可能性があります。
書くのはかなり標準的だと思います:
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つのオプションのセマンティクスは同じではありません。
アッシリアが示したように、これは意味論的な質問です。通常、Map.get(x)== nullが必要ですが、containsKeyを使用することが重要な場合があります。
そのようなケースの1つがキャッシュです。かつて、存在しないエンティティを頻繁に検索してデータベースを照会するWebアプリのパフォーマンスの問題に取り組みました。そのコンポーネントのキャッシングコードを調べたとき、cache.get(key)== nullの場合、データベースをクエリしていることに気付きました。データベースがnull(エンティティが見つからない)を返した場合、そのキー-> nullマッピングをキャッシュします。
ContainsKeyに切り替えると、null値へのマッピングが実際に何かを意味するため、問題が解決しました。 nullへのキーマッピングには、存在しないキーとは異なる意味があります。
Java8 Optionalを使用して、@ assyliasの回答をより読みやすくすることができます。
Optional.ofNullable(map.get(key)).ifPresent(value -> {
//do something with value
};)
containsKey
の後にget
が続くのは、null値が許可されないことをアプリオリに知っている場合のみ冗長です。 null値が有効でない場合、containsKey
の呼び出しはパフォーマンスに重大なペナルティがあり、以下のベンチマークに示すようにオーバーヘッドにすぎません。
Java 8 Optional
イディオム-Optional.ofNullable(map.get(key)).ifPresent
またはOptional.ofNullable(map.get(key)).ifPresent
-Vanillaの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));
}
ベンチマーク(反復)(lookupApproach)モードCntスコアエラー単位 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 MultihashTypeLupup .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
In 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を使用して、主な作業が行われるマッチングを取得します。
冗長性は、たとえばハッシュマップに格納された辞書がある場合のコンテキストです。 Wordの意味を取得したいとき
やって...
if(dictionary.containsKey(Word)) {
return dictionary.get(Word);
}
冗長です。
ただし、Wordが有効であるか、辞書に基づいていないかを確認する場合。やって...
return dictionary.get(Word) != null;
以上...
return dictionary.containsKey(Word);
冗長です。
内部でHashMapを使用する HashSet 実装をチェックする場合は、 'contains'メソッドで 'containsKey'を使用します。
public boolean contains(Object o) {
return map.containsKey(o);
}