Java Map.get y Map.containsKey

Cuando se utilizan implementaciones de mapas de Java, a veces es común invocar el Mapmétodo get (Object) y reaccionar de manera diferente en función de si el valor devuelto es nulo o no. Se puede suponer que un valor nulo devuelto por Map.get (Object) indica que no hay una entrada con la clave proporcionada en el mapa, pero no siempre es así. De hecho, si una Mapimplementación de Java permite valores nulos, entonces es posible Mapque devuelva su valor para la clave dada, pero ese valor podría ser nulo. A menudo, esto no importa, pero si lo hace, se puede usar Map.containsKey () para determinar si la Mapentrada tiene una entrada clave. Si lo hace y los Mapretornos nullen una llamada get para esa misma clave, entonces es probable que la clave se asigne a unanullvalor. En otras palabras, eso Mappodría devolver "verdadero" containsKey(Object)mientras que al mismo tiempo devolver " null" para get(Object). Hay algunas Mapimplementaciones que no permiten nullvalores. En esos casos, una nullde una llamada "get" debe coincidir consistentemente con un retorno "falso" del método "containsKey".

En esta publicación de blog, demuestro estos aspectos de  Map.get(Object)y Map.containsKey(Object). Antes de entrar en esa demostración, primero señalaré que la documentación de Javadoc para Map.get (Object) advierte explícitamente sobre las sutiles diferencias entre Map.get(Object)y Map.containsKey(Object):

Si este mapa permite valores nulos, entonces un valor de retorno de  null no indica necesariamente que el mapa no contiene ningún mapa para la clave; también es posible que el mapa asigne explícitamente la clave a  null. La  containsKey operación puede usarse para distinguir estos dos casos.

Para los ejemplos de la publicación, usaré la enumeración de Estados definida a continuación:

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 siguiente lista de código usa la enumeración anterior y llena un mapa de estados a sus ciudades capitales. El método acepta una clase que debería ser la implementación específica del mapa que se generará y completará.

generateStatesMap (Clase)

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

El método anterior se puede utilizar para generar mapas de varios tipos. No muestro el código en este momento, pero mi ejemplo crea estos mapas con cuatro implementaciones específicas: HashMap, LinkedHashMap, ConcurrentHashMap y EnumMap. Luego, cada una de estas cuatro implementaciones se ejecuta mediante el método demonstrateGetAndContains(Map), que se muestra a continuación.

demostrarGetAndContains (mapa)

/** * 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()); } 

Para esta demostración, configuré intencionalmente los mapas para que tuvieran valores de capital nulos para que Montana no tuviera ninguna entrada para Kansas. Esto ayuda a demostrar las diferencias en Map.get(Object)y Map.containsKey(Object). Debido a que no todos los tipos de implementación de Map permiten valores nulos, rodeé la parte que coloca a Montana sin capital dentro de un bloque try / catch.

Los resultados de ejecutar los cuatro tipos de mapas a través del código aparecen a continuación.

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 

Para los tres tipos de mapas para los que pude ingresar valores nulos, la llamada Map.get (Object) devuelve nulo incluso cuando el método containsKey (Object) devuelve "verdadero" para Montana porque puse esa clave en el mapa sin valor. Para Kansas, los resultados son consistentemente Map.get () devuelve nulo y Map.containsKey () devuelve "falso" porque no hay ninguna entrada en los mapas de Kansas.

El resultado anterior también demuestra que no pude poner un valor nulo para el capital de Montana en la ConcurrentHashMapimplementación (se lanzó una NullPointerException).

17 de agosto de 2010 11:23:26 p. M. Dustin.examples.MapContainsGet generateStatesMapSEVERE: java.util.concurrent.ConcurrentHashMap no permite valores nulos - java.lang.NullPointerException

Esto tuvo el efecto secundario de mantenimiento Map.get(Object)y Map.containsKey(Object)un nulo respectiva más consistente y valores de retorno falsas. En otras palabras, era imposible tener una clave en el mapa sin tener un valor no nulo correspondiente.

En muchos casos, el uso de Map.get(Object)obras según sea necesario para las necesidades particulares en cuestión, pero es mejor recordar que existen diferencias entre Map.get(Object)y Map.containsKey(Object)asegurarse de que siempre se utilice la adecuada. También es interesante notar que Map también presenta un containsValue(Object)método similar .

Enumero la lista completa de códigos para la clase MapContainsGet aquí para completar:

MapContainsGet.java