Me he estado preguntando durante algún tiempo si está permitido dentro de las mejores prácticas abstenerse de usar el containsKey()
método java.util.Map
y, en su lugar, hacer una verificación nula en el resultado de get()
.
Mi razón fundamental es que parece redundante realizar la búsqueda del valor dos veces, primero para el containsKey()
y luego nuevamente para get()
.
Por otro lado, puede ser que la mayoría de las implementaciones estándar de Map
caché la última búsqueda o que el compilador pueda eliminar la redundancia, y que para la legibilidad del código es preferible mantener la containsKey()
parte.
Agradecería mucho sus comentarios.
fuente
null
, ¿desea tratar eso de manera diferente a una clave / valor que no está establecido? Si no necesita tratarlo específicamente de manera diferente, simplemente puede usarget()
Map
es tuyoprivate
, tu clase podría garantizarnull
que nunca se inserte en el mapa. En ese caso, puede usarget()
seguido de una comprobación de nulo en lugar decontainsKey()
. Hacerlo puede ser más claro, y quizás un poco más eficiente, en algunos casos.Creo que es bastante estándar escribir:
Object value = map.get(key); if (value != null) { //do something with value }
en vez de
if (map.containsKey(key)) { Object value = map.get(key); //do something with value }
No es menos legible y un poco más eficiente, por lo que no veo ninguna razón para no hacerlo. Obviamente, si su mapa puede contener nulos, las dos opciones no tienen la misma semántica .
fuente
Como indicó Assylias, esta es una cuestión semántica. Generalmente, Map.get (x) == null es lo que desea, pero hay casos en los que es importante usar containsKey.
Uno de esos casos es un caché. Una vez trabajé en un problema de rendimiento en una aplicación web que consultaba su base de datos con frecuencia buscando entidades que no existían. Cuando estudié el código de almacenamiento en caché para ese componente, me di cuenta de que estaba consultando la base de datos si cache.get (key) == null. Si la base de datos devolvió nulo (entidad no encontrada), almacenaríamos en caché esa clave -> asignación nula.
Cambiar a containsKey resolvió el problema porque una asignación a un valor nulo en realidad significaba algo. La asignación de claves a nulo tenía un significado semántico diferente al de la clave que no existía.
fuente
containsKey
seguido de aget
es redundante solo si sabemos a priori que nunca se permitirán valores nulos. Si los valores nulos no son válidos, la invocación decontainsKey
tiene una penalización de rendimiento no trivial y es solo una sobrecarga, como se muestra en el punto de referencia a continuación.Los
Optional
modismos de Java 8 ,Optional.ofNullable(map.get(key)).ifPresent
o bienOptional.ofNullable(map.get(key)).ifPresent
, incurren en una sobrecarga no trivial en comparación con las comprobaciones nulas de vainilla.A
HashMap
usa unaO(1)
búsqueda de tabla constante mientras que aTreeMap
usa unaO(log(n))
búsqueda. ElcontainsKey
seguido de unget
modismo es mucho más lento cuando se invoca en unTreeMap
.Benchmarks
Ver 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)); }
Referencia de la fuente TreeMap
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java
Referencia de origen de HashMap
https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java
fuente
Podemos hacer que la respuesta de @assylias sea más legible con Java8 Opcional,
Optional.ofNullable(map.get(key)).ifPresent(value -> { //do something with value };)
fuente
En Java si verificas la implementación
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; }
ambos usan getNode para recuperar la coincidencia, donde se realiza el trabajo principal.
la redundancia es contextual, por ejemplo, si tiene un diccionario almacenado en un mapa hash. Cuando quieres recuperar el significado de una palabra
haciendo...
if(dictionary.containsKey(word)) { return dictionary.get(word); }
es redundante.
pero si quieres comprobar si una palabra es válida o no está basada en el diccionario. haciendo...
return dictionary.get(word) != null;
terminado...
return dictionary.containsKey(word);
es redundante.
Si verifica la implementación de HashSet , que usa HashMap internamente, use 'containsKey' en el método 'contains'.
public boolean contains(Object o) { return map.containsKey(o); }
fuente