web-dev-qa-db-ja.com

map.get()を使用する場合、Java Map.containsKey()冗長)を使用しています

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

私の理論的根拠は、最初にcontainsKey()に対して、次にget()に対して値のルックアップを2回行うのは冗長に思えるということです。

一方、Mapのほとんどの標準実装は最後のルックアップをキャッシュするか、コンパイラが冗長性を排除できる可能性があり、コードの可読性のためにcontainsKey()パート。

コメントをお願いします。

82
Erik Madsen

一部のMap実装では、HashMapなどのnull値を使用できます。この場合、get(key)nullを返す場合、このキーに関連付けられたマップにエントリがないことを保証しません。

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

104

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

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つのオプションのセマンティクスは同じではありません

39
assylias

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

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

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

6
Brandon

Java8 Optionalを使用して、@ assyliasの回答をより読みやすくすることができます。

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)
2
Raja
  • containsKeyの後にgetが続くのは、null値が許可されないことをアプリオリに知っている場合のみ冗長です。 null値が有効でない場合、containsKeyの呼び出しはパフォーマンスに重大なペナルティがあり、以下のベンチマークに示すようにオーバーヘッドにすぎません。

  • Java 8 Optionalイディオム-Optional.ofNullable(map.get(key)).ifPresentまたはOptional.ofNullable(map.get(key)).ifPresent-Vanillaのnullチェックだけと比較して、重要なオーバーヘッドが発生します。

  • HashMapO(1)定数テーブル検索を使用しますが、TreeMapO(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 

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

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);
    }
1
asela38