map.get()を使用する場合、java Map.containsKey()を使用していますか?


90

私はしばらくの間、containsKey()メソッドの使用を控えjava.util.Map、代わりにからの結果に対してnullチェックを実行することがベストプラクティス内で許可されるかどうか疑問に思っていましたget()

私の根拠は、値の2倍の検索を行うには冗長思われることである-のための最初containsKey()と再度のためにget()

一方Map、最後のルックアップをキャッシュするほとんどの標準的な実装、またはコンパイラーが冗長性を排除できること、およびコードを読みやすくするために、containsKey()パーツを維持することが望ましい場合があります。

コメントありがとうございました。

回答:


112

HashMapなどの一部のMap実装は、null値を許可されています。この場合、get(key)戻り値が返さnullれても、このキーに関連付けられているマップにエントリがないことは保証されません。

ですから、マップがキーの使用が含まれているかどうかを知りたい場合Map.containsKey。キーにマップされた値が必要な場合は、を使用しますMap.get(key)。このマップがnull値を許可する場合、nullの戻り値は、マップがキーのマッピングを含まないことを必ずしも示すわけではありません。そのような場合Map.containsKeyは役に立たず、パフォーマンスに影響します。さらに、マップへの同時アクセス(例ConcurrentHashMap:)の場合、テストした後、Map.containsKey(key)を呼び出す前に別のスレッドによってエントリが削除される可能性がありますMap.get(key)


8
値がに設定されている場合でも、設定されてnullいないキー/値とは異なる方法で処理しますか?特に異なる扱いをする必要がない場合は、次のように使用できますget()
Peter Lawrey

1
あなたの場合Mapprivate、あなたのクラスは、保証することができるかもしれませんnullマップに挿入されることはありません。その場合、のget()代わりにnullのチェックを続けることができますcontainsKey()。そうすることで、場合によっては、より明確になり、おそらくもう少し効率的になります。
Raedwald 2013

44

書くのはかなり標準的だと思います:

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つのオプションが同じ意味を持っていません


8

アサイリアが示したように、これは意味の質問です。通常、Map.get(x)== nullが必要ですが、containsKeyを使用することが重要な場合があります。

そのようなケースの1つがキャッシュです。データベースにクエリを実行して、存在しないエンティティを頻繁に検索するWebアプリで、パフォーマンスの問題に取り組んだことがありました。そのコンポーネントのキャッシングコードを調べたところ、cache.get(key)== nullの場合、データベースをクエリしていることがわかりました。データベースがnull(エンティティが見つからない)を返した場合、そのキー-> nullマッピングをキャッシュします。

containsKeyへの切り替えは、null値へのマッピングが実際には何かを意味するため、問題を解決しました。nullへのキーのマッピングは、存在しないキーとは意味が異なります。


面白い。値をキャッシュする前に、単にnullチェックを追加しなかったのはなぜですか?
Saket 2015

それは何も変更しません。ポイントは、nullへのキーマッピングは、「これは既に実行されています。キャッシュされています。値 nullです」という意味です。特定のキーがまったく含まれていない場合と、「キャッシュ内にないため、DBを確認する必要があるかもしれません」という意味です。
ブランドン

4
  • containsKey後に続くa getは、null値が許可されないことがアプリオリにわかっている場合にのみ冗長です。null値が有効でない場合、の呼び出しはcontainsKey重要なパフォーマンスペナルティを伴い、以下のベンチマークに示すようにオーバーヘッドになります。

  • Java 8のOptionalイディオムは- Optional.ofNullable(map.get(key)).ifPresentまたはOptional.ofNullable(map.get(key)).ifPresent-単なるバニラnullチェックと比較して重要なオーバーヘッドを招きます。

  • A HashMapO(1)定数テーブルルックアップを使用しますが、A はルックアップを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
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


3

@assyliasの回答をJava8オプションで読みやすくすることができます。

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)

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);
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.