web-dev-qa-db-fra.com

Utilise Java Map.containsKey () redondant lors de l'utilisation de map.get ()

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.

79
Erik Madsen

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).

101
Evgeniy Dorofeev

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 .

39
assylias

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.

5
Brandon

Nous pouvons rendre la réponse de @assylias plus lisible avec Java8 Facultatif,

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

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

Des repères

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 

Référence de source TreeMap

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

Référence de source HashMap

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