Java Map.get et Map.containsKey

Lors de l'utilisation des implémentations de Java Map, il est parfois courant d'appeler la Mapméthode get (Object) de s et de réagir différemment selon que la valeur renvoyée est nulle ou non. Une hypothèse courante pourrait être faite qu'un null retourné par Map.get (Object) indique qu'il n'y a pas d'entrée avec la clé fournie dans la carte, mais ce n'est pas toujours le cas. En effet, si une Mapimplémentation Java autorise des valeurs nulles, alors il est possible pour le Mapde renvoyer sa valeur pour la clé donnée, mais cette valeur peut être nulle. Souvent, cela n'a pas d'importance, mais si c'est le cas, on peut utiliser Map.containsKey () pour déterminer si l' Mapentrée a une entrée de clé. Si c'est le cas et que les Mapretours nullsur un appel get pour cette même clé, il est probable que la clé mappe à unnullvaleur. En d'autres termes, cela Mappourrait renvoyer "vrai" pendant containsKey(Object)tout en retournant " null" pour get(Object). Certaines Mapimplémentations n'autorisent pas les nullvaleurs. Dans ces cas, un nullappel "get" doit toujours correspondre à un retour "false" de la méthode "containsKey".

Dans cet article de blog, je démontre ces aspects de  Map.get(Object)et Map.containsKey(Object). Avant d'entrer dans cette démonstration, je soulignerai d'abord que la documentation Javadoc pour Map.get (Object) avertit explicitement des différences subtiles entre Map.get(Object)et Map.containsKey(Object):

Si ce mappage autorise des valeurs nulles, une valeur de retour de  null n'indique pas nécessairement que le mappage ne contient aucun mappage pour la clé; il est également possible que la carte mappe explicitement la clé sur  null. L'  containsKey opération peut être utilisée pour distinguer ces deux cas.

Pour les exemples de l'article, j'utiliserai l'énumération States définie ensuite:

States.java

package dustin.examples; /** * Enum representing select western states in the United Sates. */ public enum States { ARIZONA("Arizona"), CALIFORNIA("California"), COLORADO("Colorado"), IDAHO("Idaho"), KANSAS("Kansas"), MONTANA("Montana"), NEVADA("Nevada"), NEW_MEXICO("New Mexico"), NORTH_DAKOTA("North Dakota"), OREGON("Oregon"), SOUTH_DAKOTA("South Dakota"), UTAH("Utah"), WASHINGTON("Washington"), WYOMING("Wyoming"); /** State name. */ private String stateName; /** * Parameterized enum constructor accepting a state name. * * @param newStateName Name of the state. */ States(final String newStateName) { this.stateName = newStateName; } /** * Provide the name of the state. * * @return Name of the state */ public String getStateName() { return this.stateName; } } 

La liste de codes suivante utilise l'énumération ci-dessus et remplit une carte des états de leurs capitales. La méthode accepte une classe qui doit être l'implémentation spécifique de Map à générer et à remplir.

generateStatesMap (Classe)

/** * Generate and populate a Map of states to capitals with provided Map type. * This method also logs any Map implementations for which null values are * not allowed. * * @param mapClass Type of Map to be generated. * @return Map of states to capitals. */ private static Map generateStatesMap(Class mapClass) { Map mapToPopulate = null; if (Map.class.isAssignableFrom(mapClass)) { try { mapToPopulate = mapClass != EnumMap.class ? (Map) mapClass.newInstance() : getEnumMap(); mapToPopulate.put(States.ARIZONA, "Phoenix"); mapToPopulate.put(States.CALIFORNIA, "Sacramento"); mapToPopulate.put(States.COLORADO, "Denver"); mapToPopulate.put(States.IDAHO, "Boise"); mapToPopulate.put(States.NEVADA, "Carson City"); mapToPopulate.put(States.NEW_MEXICO, "Sante Fe"); mapToPopulate.put(States.NORTH_DAKOTA, "Bismark"); mapToPopulate.put(States.OREGON, "Salem"); mapToPopulate.put(States.SOUTH_DAKOTA, "Pierre"); mapToPopulate.put(States.UTAH, "Salt Lake City"); mapToPopulate.put(States.WASHINGTON, "Olympia"); mapToPopulate.put(States.WYOMING, "Cheyenne"); try { mapToPopulate.put(States.MONTANA, null); } catch (NullPointerException npe) { LOGGER.severe( mapToPopulate.getClass().getCanonicalName() + " does not allow for null values - " + npe.toString()); } } catch (InstantiationException instantiationException) { LOGGER.log( Level.SEVERE, "Unable to instantiate Map of type " + mapClass.getName() + instantiationException.toString(), instantiationException); } catch (IllegalAccessException illegalAccessException) { LOGGER.log( Level.SEVERE, "Unable to access Map of type " + mapClass.getName() + illegalAccessException.toString(), illegalAccessException); } } else { LOGGER.warning("Provided data type " + mapClass.getName() + " is not a Map."); } return mapToPopulate; } 

La méthode ci-dessus peut être utilisée pour générer des cartes de différentes sortes. Je ne montre pas le code pour le moment, mais mon exemple crée ces cartes avec quatre implémentations spécifiques: HashMap, LinkedHashMap, ConcurrentHashMap et EnumMap. Chacune de ces quatre implémentations est ensuite exécutée via la méthode demonstrateGetAndContains(Map), qui est illustrée ci-après.

démontrerGetAndContains (carte)

/** * Demonstrate Map.get(States) and Map.containsKey(States). * * @param map Map upon which demonstration should be conducted. */ private static void demonstrateGetAndContains(final Map map) { final StringBuilder demoResults = new StringBuilder(); final String mapType = map.getClass().getCanonicalName(); final States montana = States.MONTANA; demoResults.append(NEW_LINE); demoResults.append( "Map of type " + mapType + " returns " + (map.get(montana)) + " for Map.get() using " + montana.getStateName()); demoResults.append(NEW_LINE); demoResults.append( "Map of type " + mapType + " returns " + (map.containsKey(montana)) + " for Map.containsKey() using " + montana.getStateName()); demoResults.append(NEW_LINE); final States kansas = States.KANSAS; demoResults.append( "Map of type " + mapType + " returns " + (map.get(kansas)) + " for Map.get() using " + kansas.getStateName()); demoResults.append(NEW_LINE); demoResults.append( "Map of type " + mapType + " returns " + (map.containsKey(kansas)) + " for Map.containsKey() using " + kansas.getStateName()); demoResults.append(NEW_LINE); LOGGER.info(demoResults.toString()); } 

Pour cette démonstration, j'ai intentionnellement configuré les cartes pour avoir des valeurs de capital nulles pour le Montana afin qu'elles n'aient aucune entrée pour le Kansas. Cela aide à démontrer les différences entre Map.get(Object)et Map.containsKey(Object). Étant donné que tous les types d'implémentation de Map ne permettent pas de valeurs nulles, j'ai entouré la partie qui place Montana sans majuscule dans un bloc try / catch.

Les résultats de l'exécution des quatre types de cartes via le code apparaissent ensuite.

Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo INFO: HashMap: {MONTANA=null, WASHINGTON=Olympia, ARIZONA=Phoenix, CALIFORNIA=Sacramento, WYOMING=Cheyenne, SOUTH_DAKOTA=Pierre, COLORADO=Denver, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, NEVADA=Carson City, OREGON=Salem, UTAH=Salt Lake City, IDAHO=Boise} Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains INFO: Map of type java.util.HashMap returns null for Map.get() using Montana Map of type java.util.HashMap returns true for Map.containsKey() using Montana Map of type java.util.HashMap returns null for Map.get() using Kansas Map of type java.util.HashMap returns false for Map.containsKey() using Kansas Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo INFO: LinkedHashMap: {ARIZONA=Phoenix, CALIFORNIA=Sacramento, COLORADO=Denver, IDAHO=Boise, NEVADA=Carson City, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, OREGON=Salem, SOUTH_DAKOTA=Pierre, UTAH=Salt Lake City, WASHINGTON=Olympia, WYOMING=Cheyenne, MONTANA=null} Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains INFO: Map of type java.util.LinkedHashMap returns null for Map.get() using Montana Map of type java.util.LinkedHashMap returns true for Map.containsKey() using Montana Map of type java.util.LinkedHashMap returns null for Map.get() using Kansas Map of type java.util.LinkedHashMap returns false for Map.containsKey() using Kansas Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet generateStatesMap SEVERE: java.util.concurrent.ConcurrentHashMap does not allow for null values - java.lang.NullPointerException Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo INFO: ConcurrentHashMap: {SOUTH_DAKOTA=Pierre, ARIZONA=Phoenix, WYOMING=Cheyenne, UTAH=Salt Lake City, OREGON=Salem, CALIFORNIA=Sacramento, IDAHO=Boise, NEW_MEXICO=Sante Fe, COLORADO=Denver, NORTH_DAKOTA=Bismark, WASHINGTON=Olympia, NEVADA=Carson City} Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains INFO: Map of type java.util.concurrent.ConcurrentHashMap returns null for Map.get() using Montana Map of type java.util.concurrent.ConcurrentHashMap returns false for Map.containsKey() using Montana Map of type java.util.concurrent.ConcurrentHashMap returns null for Map.get() using Kansas Map of type java.util.concurrent.ConcurrentHashMap returns false for Map.containsKey() using Kansas Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo INFO: EnumMap: {ARIZONA=Phoenix, CALIFORNIA=Sacramento, COLORADO=Denver, IDAHO=Boise, MONTANA=null, NEVADA=Carson City, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, OREGON=Salem, SOUTH_DAKOTA=Pierre, UTAH=Salt Lake City, WASHINGTON=Olympia, WYOMING=Cheyenne} Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains INFO: Map of type java.util.EnumMap returns null for Map.get() using Montana Map of type java.util.EnumMap returns true for Map.containsKey() using Montana Map of type java.util.EnumMap returns null for Map.get() using Kansas Map of type java.util.EnumMap returns false for Map.containsKey() using Kansas 

Pour les trois types de carte pour lesquels j'ai pu saisir des valeurs nulles, l'appel Map.get (Object) retourne null même lorsque la méthode containsKey (Object) renvoie "true" pour Montana car j'ai mis cette clé dans la carte sans valeur. Pour le Kansas, les résultats sont systématiquement. Map.get () renvoie null et Map.containsKey () renvoie «false» car il n'y a aucune entrée dans les cartes du Kansas.

La sortie ci-dessus démontre également que je ne pouvais pas mettre une valeur nulle pour le capital du Montana dans l' ConcurrentHashMapimplémentation (une NullPointerException a été lancée).

17 août 2010 23:23:26 dustin.examples.MapContainsGet generateStatesMapSEVERE: java.util.concurrent.ConcurrentHashMap ne permet pas les valeurs nulles - java.lang.NullPointerException

Cela a eu pour effet secondaire de maintien Map.get(Object)et Map.containsKey(Object)une valeur nulle respective plus cohérente et les fausses valeurs de retour. En d'autres termes, il était impossible d'avoir une clé dans la carte sans avoir une valeur non nulle correspondante.

Dans de nombreux cas, l'utilisation des Map.get(Object)œuvres au besoin pour les besoins particuliers du moment, mais il est préférable de se rappeler qu'il existe des différences entre Map.get(Object)et Map.containsKey(Object)pour s'assurer que celle qui convient est toujours utilisée. Il est également intéressant de noter que Map propose également une containsValue(Object)méthode similaire .

Je liste la liste complète du code pour la classe MapContainsGet ici par souci d'exhaustivité:

MapContainsGet.java