it-swarm.com.de

Ist Java Map.containsKey () redundant, wenn Map.get () verwendet wird

Ich habe mich schon länger gefragt, ob es im Rahmen der bewährten Methode zulässig ist, die containsKey()-Methode für Java.util.Map nicht zu verwenden und stattdessen das Ergebnis von get() auf Null zu überprüfen. 

Mein Grund ist, dass es überflüssig erscheint, den Wert zweimal zu suchen - zuerst für die containsKey() und dann noch einmal für get().

Andererseits kann es sein, dass die meisten Standardimplementierungen von Map die letzte Suche zwischenspeichern oder dass der Compiler die Redundanz anderweitig aufheben kann, und dass aus Gründen der Lesbarkeit des Codes der Teil containsKey() vorzuziehen ist.

Ich würde mich sehr über Ihre Kommentare freuen.

79
Erik Madsen

Einige Map-Implementierungen dürfen Nullwerte aufweisen, z. B. HashMap. Wenn get(key)null zurückgibt, kann nicht garantiert werden, dass in der Map kein Eintrag vorhanden ist, der diesem Schlüssel zugeordnet ist. 

Wenn Sie also wissen möchten, ob eine Karte einen Schlüssel enthält, verwenden Sie Map.containsKey. Wenn Sie einfach einen Wert benötigen, der einer Taste zugeordnet ist, verwenden Sie Map.get(key). Wenn diese Zuordnung Nullwerte zulässt, bedeutet der Rückgabewert Null nicht notwendigerweise, dass die Zuordnung keine Zuordnung für den Schlüssel enthält. In diesem Fall ist Map.containsKey unbrauchbar und beeinflusst die Leistung. Bei gleichzeitigem Zugriff auf eine Karte (z. B. ConcurrentHashMap) besteht nach dem Testen von Map.containsKey(key) die Möglichkeit, dass der Eintrag von einem anderen Thread entfernt wird, bevor Sie Map.get(key) aufrufen.

101

Ich denke, es ist ziemlich normal zu schreiben:

Object value = map.get(key);
if (value != null) {
    //do something with value
}

anstatt 

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

Es ist nicht weniger lesbar und etwas effizienter, daher sehe ich keinen Grund, es nicht zu tun. Offensichtlich Wenn Ihre Map null enthalten kann, haben die beiden Optionen nicht dieselbe Semantik .

39
assylias

Wie Assylien angedeutet haben, ist dies eine semantische Frage. Im Allgemeinen ist Map.get (x) == null das, was Sie möchten, aber es gibt Fälle, in denen die Verwendung von containKey wichtig ist.

Ein solcher Fall ist ein Cache. Ich habe einmal an einem Leistungsproblem in einer Web-App gearbeitet, bei dem die Datenbank abgefragt wurde, und häufig nach nicht vorhandenen Entitäten gesucht wurde. Als ich den Cache-Code für diese Komponente studierte, stellte ich fest, dass die Datenbank abgefragt wurde, wenn cache.get (key) == null ist. Wenn die Datenbank null zurückgibt (Entität nicht gefunden), würden wir diese Zuordnung von Schlüssel zu Null zwischenspeichern.

Durch das Wechseln zu containKey wurde das Problem gelöst, da eine Zuordnung zu einem Nullwert tatsächlich etwas bedeutete. Die Schlüsselzuordnung auf null hatte eine andere semantische Bedeutung als der nicht vorhandene Schlüssel.

5
Brandon

Wir können @assylias mit Java8 lesbarer machen. Optional,

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

In Java, wenn Sie die Implementierung überprüfen

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;
}

beide verwenden getNode, um den Abgleich abzurufen, wo die Hauptarbeit erledigt wird.

redundanz ist beispielsweise kontextbezogen, wenn Sie ein Wörterbuch in einer Hash-Map gespeichert haben. Wenn Sie die Bedeutung eines Wortes abrufen möchten

tun...

if(dictionary.containsKey(Word)) {
   return dictionary.get(Word);
}

ist überflüssig.

wenn Sie jedoch prüfen möchten, ob ein Word gültig ist oder nicht auf dem Wörterbuch basiert. tun...

 return dictionary.get(Word) != null;

Über...

 return dictionary.containsKey(Word);

ist überflüssig. 

Wenn Sie die HashSet -Implementierung überprüfen, die HashMap intern verwendet, verwenden Sie die Methode 'containsKey' in 'enthält'. 

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
1
asela38
  • containsKey gefolgt von einer get ist nur dann redundant, wenn wir wissen, dass Nullwerte niemals zulässig sind. Wenn NULL-Werte nicht gültig sind, hat der Aufruf von containsKey eine nicht unerhebliche Leistungseinbusse und ist, wie in der nachstehenden Benchmark gezeigt, nur Overhead.

  • Die Java 8 Optional-Idiome - Optional.ofNullable(map.get(key)).ifPresent oder Optional.ofNullable(map.get(key)).ifPresent - haben einen nicht unerheblichen Overhead im Vergleich zu nur Vanilla-Nullprüfungen.

  • Eine HashMap verwendet eine O(1)-Konstantentabelle, während eine TreeMap eine O(log(n))-Suche verwendet. Die containsKey, gefolgt von einer get-Idiom, ist viel langsamer, wenn sie für eine TreeMap aufgerufen wird.

Benchmarks

Siehe 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 (Iterationen) (LookupApproach) Modus 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 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 h3 Durchsatz 9 16.780 ± 0.719 us/op 

TreeMap-Quellenreferenz

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/Java/util/TreeMap.Java

HashMap-Quellenreferenz

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/Java/util/HashMap.Java