Je me demande depuis quelque temps s’il est acceptable, dans le cadre des pratiques optimales, de ne pas utiliser la méthode containsKey()
sur Java.util.Map
et de procéder à une vérification de nullité du résultat de get()
.
Mon raisonnement est qu’il semble redondant de rechercher la valeur deux fois, d’abord pour la fonction containsKey()
, puis pour la fonction get()
.
D'un autre côté, il est possible que la plupart des implémentations standard de Map
mettent en cache la dernière recherche ou que le compilateur puisse autrement supprimer la redondance, et qu'il est préférable de conserver la partie containsKey()
pour la lisibilité du code.
J'apprécierais beaucoup vos commentaires.
Certaines implémentations de carte sont autorisées à avoir des valeurs NULL, par exemple HashMap. Dans ce cas, si get(key)
renvoie null
, cela ne garantit pas qu'il n'y a aucune entrée dans la carte associée à cette clé.
Donc, si vous voulez savoir si une carte contient une clé, utilisez Map.containsKey
. Si vous avez simplement besoin d’une valeur mappée sur une clé, utilisez Map.get(key)
. Si cette carte autorise les valeurs NULL, une valeur renvoyée de null n'indique pas nécessairement que la carte ne contient pas de mappage pour la clé. Dans ce cas, Map.containsKey
est inutile et affectera les performances. De plus, en cas d'accès simultané à une carte (par exemple, ConcurrentHashMap
), après avoir testé Map.containsKey(key)
, il est possible que l'entrée soit supprimée par un autre thread avant d'appeler Map.get(key)
.
Je pense que c'est assez standard d'écrire:
Object value = map.get(key);
if (value != null) {
//do something with value
}
au lieu de
if (map.containsKey(key)) {
Object value = map.get(key);
//do something with value
}
Ce n'est pas moins lisible et légèrement plus efficace, je ne vois donc aucune raison de ne pas le faire. Évidemment si votre carte peut contenir null, les deux options n'ont pas la même sémantique .
Comme indiqué par Assylias, il s'agit d'une question sémantique. En règle générale, Map.get (x) == null correspond à vos souhaits, mais il est parfois important d’utiliser contientKey.
Un tel cas est un cache. Une fois, j'ai travaillé sur un problème de performance dans une application Web qui interrogeait souvent sa base de données à la recherche d'entités inexistantes. Lorsque j'ai étudié le code de mise en cache pour ce composant, j'ai réalisé qu'il interrogeait la base de données si cache.get (clé) == null. Si la base de données renvoyait la valeur null (entité non trouvée), nous mettions en cache cette correspondance clé -> null.
Le fait de passer à containsKey a résolu le problème car un mappage sur une valeur null signifiait réellement quelque chose. Le mappage de la clé sur null avait une signification sémantique différente de celle de la clé inexistante.
Nous pouvons rendre la réponse de @assylias plus lisible avec Java8 Facultatif,
Optional.ofNullable(map.get(key)).ifPresent(value -> {
//do something with value
};)
En Java si vous vérifiez l'implémentation
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;
}
les deux utilisent getNode pour récupérer la correspondance, où le travail principal est effectué.
la redondance est contextuelle, par exemple si vous avez un dictionnaire stocké dans une carte de hachage. Quand vous voulez récupérer le sens d'un mot
faire...
if(dictionary.containsKey(Word)) {
return dictionary.get(Word);
}
est redondant.
mais si vous voulez vérifier un mot est valide ou non basé sur le dictionnaire. Faire...
return dictionary.get(Word) != null;
plus de...
return dictionary.containsKey(Word);
est redondant.
Si vous vérifiez HashSet implementation, qui utilise HashMap en interne, utilisez la méthode
public boolean contains(Object o) {
return map.containsKey(o);
}
containsKey
suivi de get
n'est redondant que si nous savons a priori que les valeurs nulles ne seront jamais autorisées. Si les valeurs null ne sont pas valides, l'invocation de containsKey
entraîne une pénalité de performance non triviale et constitue simplement une surcharge, comme indiqué dans le repère ci-dessous.
Les idiomes Optional
de Java 8 - Optional.ofNullable(map.get(key)).ifPresent
ou Optional.ofNullable(map.get(key)).ifPresent
- entraînent une surcharge non triviale par rapport aux simples vérifications null de Vanilla.
HashMap
utilise une table de recherche constante O(1)
tandis que TreeMap
utilise une table de recherche O(log(n))
. La variable containsKey
suivie d'un idiome get
est beaucoup plus lente lorsqu'elle est invoquée sur une variable TreeMap
.
Voir 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 (itérations) (lookupApproach) Mode Cnt Marquer des unités d'erreur MultihashTypeLookupBenchmark.testLookup 1000 t1 moyen 9 33.438 ± 4.514 us/op MultihashTypeLookupBenchmark.testLookup.testLookup 9 t. MultihashTypeLookupBenchmark.testLookup 1000 avgt 9 39.259 ± 1.306 us/op MultihashTypeLookupBenchmark.testLookup 1000 h1 avgt 9 18,954 ± 0,414 us/op .MultihashTypeLookupBenchmark.testLookup 1000 h3 moy 9 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