Java Map.get e Map.containsKey

Quando si utilizzano le implementazioni Map di Java, a volte è comune invocare il Mapmetodo get (Object) e reagire in modo diverso a seconda che il valore restituito sia nullo o meno. Si potrebbe ipotizzare che un valore null restituito da Map.get (Object) indichi che non è presente alcuna voce con la chiave fornita nella mappa, ma non è sempre così. Infatti, se Mapun'implementazione Java consente valori nulli, è possibile Mapche restituisca il suo valore per la chiave data, ma quel valore potrebbe essere nullo. Spesso questo non ha importanza, ma in tal caso è possibile utilizzare Map.containsKey () per determinare se la Mapvoce ha una voce chiave. Se lo fa e viene Maprestituito nulluna chiamata get per la stessa chiave, è probabile che la chiave sia mappata a un filenullvalore. In altre parole, Mappotrebbe restituire "vero" per containsKey(Object)e allo stesso tempo restituire " null" per get(Object). Ci sono alcune Mapimplementazioni che non consentono nullvalori. In questi casi, una nullchiamata "get" deve corrispondere in modo coerente a una restituzione "false" dal metodo "containsKey".

In questo post del blog, mostro questi aspetti di  Map.get(Object)e Map.containsKey(Object). Prima di entrare in quella dimostrazione, sottolineerò innanzitutto che la documentazione Javadoc per Map.get (Object) avverte esplicitamente delle sottili differenze tra Map.get(Object)e Map.containsKey(Object):

Se questa mappa consente valori nulli, un valore restituito di  null non indica necessariamente che la mappa non contiene alcuna mappatura per la chiave; è anche possibile che la mappa associ esplicitamente la chiave a  null. L'  containsKey operazione può essere utilizzata per distinguere questi due casi.

Per gli esempi del post, userò l'enumerazione States definita di seguito:

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

L'elenco di codice successivo utilizza l'enumerazione sopra e popola una mappa degli stati per le loro capitali. Il metodo accetta una classe che dovrebbe essere l'implementazione specifica di Map da generare e popolare.

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

Il metodo sopra può essere utilizzato per generare mappe di vari tipi. Al momento non mostro il codice, ma il mio esempio crea queste mappe con quattro implementazioni specifiche: HashMap, LinkedHashMap, ConcurrentHashMap ed EnumMap. Ciascuna di queste quattro implementazioni viene quindi eseguita attraverso il metodo demonstrateGetAndContains(Map), mostrato di seguito.

dimostrareGetAndContains (mappa)

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

Per questa dimostrazione, ho impostato intenzionalmente le mappe in modo che abbiano valori di capitale nulli per il Montana in modo che non abbia alcuna voce per Kansas. Questo aiuta a dimostrare le differenze in Map.get(Object)e Map.containsKey(Object). Poiché non tutti i tipi di implementazione della mappa consentono valori nulli, ho circondato la parte che mette Montana senza maiuscola all'interno di un blocco try / catch.

Successivamente vengono visualizzati i risultati dell'esecuzione dei quattro tipi di mappe tramite il codice.

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 

Per i tre tipi di mappa per i quali ho potuto inserire valori nulli, la chiamata Map.get (Object) restituisce null anche quando il metodo containsKey (Object) restituisce "true" per Montana perché ho inserito quella chiave nella mappa senza un valore. Per il Kansas, i risultati sono sempre Map.get () restituisce null e Map.containsKey () restituisce "false" perché non è presente alcuna voce in Maps for Kansas.

L'output sopra mostra anche che non è stato possibile inserire un valore nullo per il capitale del Montana ConcurrentHashMapnell'implementazione (è stata generata un'eccezione NullPointerException).

17 agosto 2010 23:23:26 dustin.examples.MapContainsGet generateStatesMapSEVERE: java.util.concurrent.ConcurrentHashMap non consente valori nulli - java.lang.NullPointerException

Questo ha l'effetto collaterale di tenuta Map.get(Object)ed Map.containsKey(Object)un rispettivo nullo più coerente e valori restituiti falsi. In altre parole, era impossibile avere una chiave nella mappa senza un valore corrispondente non nullo.

In molti casi, utilizzare le Map.get(Object)opere secondo necessità per le particolari esigenze a portata di mano, ma è meglio ricordare che ci sono differenze tra Map.get(Object)e Map.containsKey(Object)assicurarsi che venga sempre utilizzata quella appropriata. È anche interessante notare che anche Map presenta un containsValue(Object)metodo simile .

Elencho l'intero listato di codice per la classe MapContainsGet qui per completezza:

MapContainsGet.java