Zastanawiałem się od jakiegoś czasu, czy w ramach najlepszej praktyki można powstrzymać się od używania containsKey()
metody na java.util.Map
i zamiast tego sprawdzić wynik z get()
.
Moje uzasadnienie jest takie, że dwukrotne sprawdzanie wartości wydaje się zbędne - najpierw dla, containsKey()
a potem ponownie dla get()
.
Z drugiej strony może się zdarzyć, że większość standardowych implementacji Map
pamięci podręcznej ostatnie wyszukiwanie lub kompilator może w inny sposób pozbyć się nadmiarowości, a dla czytelności kodu preferowane jest zachowanie containsKey()
części.
Byłbym bardzo wdzięczny za twoje komentarze.
źródło
null
, czy chcesz traktować ją inaczej niż klucz / wartość, która nie jest ustawiona? Jeśli nie musisz specjalnie traktować tego inaczej, możesz po prostu użyćget()
Map
takprivate
, Twoja klasa może być w stanie zagwarantować, że anull
nigdy nie zostanie wstawiony na mapę. W takim przypadku możesz użyć,get()
a następnie sprawdzić wartość null zamiastcontainsKey()
. W niektórych przypadkach może to być jaśniejsze i być może trochę bardziej wydajne.Myślę, że pisanie jest dość standardowe:
Object value = map.get(key); if (value != null) { //do something with value }
zamiast
if (map.containsKey(key)) { Object value = map.get(key); //do something with value }
Nie jest mniej czytelny i nieco wydajniejszy, więc nie widzę powodów, żeby tego nie robić. Oczywiście, jeśli twoja mapa może zawierać wartość null, obie opcje nie mają tej samej semantyki .
źródło
Jak wskazali asyliowie, jest to pytanie semantyczne. Generalnie Map.get (x) == null jest tym, czego potrzebujesz, ale są przypadki, w których ważne jest, aby użyć containsKey.
Jednym z takich przypadków jest pamięć podręczna. Kiedyś pracowałem nad problemem z wydajnością w aplikacji internetowej, która często wykonywała zapytania w swojej bazie danych, szukając jednostek, które nie istnieją. Kiedy przestudiowałem kod pamięci podręcznej dla tego komponentu, zdałem sobie sprawę, że odpytuje bazę danych, jeśli cache.get (klucz) == null. Jeśli baza danych zwróci wartość null (nie znaleziono jednostki), będziemy buforować ten klucz -> mapowanie null.
Przełączenie na includeKey rozwiązało problem, ponieważ mapowanie na wartość null w rzeczywistości coś oznaczało. Mapowanie klucza na null miało inne znaczenie semantyczne niż klucz nieistniejący.
źródło
containsKey
po którym następuje aget
jest zbędne tylko wtedy, gdy wiemy, apriori, że wartości null nigdy nie będą dozwolone. Jeśli wartości null nie są prawidłowe, wywołaniecontainsKey
ma nietrywialny spadek wydajności i jest tylko narzutem, jak pokazano w poniższym benchmarku.Optional
Idiomy Java 8 -Optional.ofNullable(map.get(key)).ifPresent
lubOptional.ofNullable(map.get(key)).ifPresent
- wiążą się z nietrywialnym narzutem w porównaniu z zwykłymi sprawdzeniami zerowymi.A
HashMap
używaO(1)
stałego wyszukiwania w tabeli, podczas gdy aTreeMap
używaO(log(n))
wyszukiwania.containsKey
Następnieget
idiomu jest znacznie wolniejsze, gdy wywoływany na zasadzieTreeMap
.Benchmarki
Zobacz 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)); }
Odwołanie do źródła TreeMap
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java
Odniesienie do źródła HashMap
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java
źródło
Możemy sprawić, że odpowiedź @assylias będzie bardziej czytelna dzięki Java8 Optional,
Optional.ofNullable(map.get(key)).ifPresent(value -> { //do something with value };)
źródło
W Javie, jeśli sprawdzisz implementację
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; }
oba używają getNode do pobrania dopasowania, gdzie główna praca jest wykonywana.
redundancja jest kontekstowa, na przykład, jeśli masz słownik przechowywany na mapie skrótów. Gdy chcesz odzyskać znaczenie słowa
robić...
if(dictionary.containsKey(word)) { return dictionary.get(word); }
jest zbędny.
ale jeśli chcesz sprawdzić, czy słowo jest prawidłowe lub nie na podstawie słownika. robić...
return dictionary.get(word) != null;
nad...
return dictionary.containsKey(word);
jest zbędny.
Jeśli zaznaczysz implementację HashSet , która używa HashMap wewnętrznie, użyj metody „ zawieraKlucz ” w metodzie „zawiera”.
public boolean contains(Object o) { return map.containsKey(o); }
źródło