¿Es posible HashMap
devolver un valor predeterminado para todas las claves que no se encuentran en el conjunto?
¿Es posible HashMap
devolver un valor predeterminado para todas las claves que no se encuentran en el conjunto?
Respuestas:
[Actualizar]
Como lo señalaron otras respuestas y comentaristas, a partir de Java 8 simplemente puede llamar Map#getOrDefault(...)
.
[Original]
No hay una implementación de Map que haga esto exactamente, pero sería trivial implementar la suya propia extendiendo HashMap:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
protected V defaultValue;
public DefaultHashMap(V defaultValue) {
this.defaultValue = defaultValue;
}
@Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
}
}
(v == null)
a (v == null && !this.containsKey(k))
en caso de que agreguen un null
valor a propósito . Lo sé, este es solo un caso de esquina, pero el autor puede encontrarse con él.
!this.containsValue(null)
. Esto es sutilmente diferente de !this.containsKey(k)
. La containsValue
solución fallará si a alguna otra clave se le ha asignado explícitamente un valor de null
. Por ejemplo: map = new HashMap(); map.put(k1, null); V v = map.get(k2);
en este caso, v
seguirá siendo null
, ¿correcto?
En Java 8, use Map.getOrDefault . Se necesita la clave y el valor para devolver si no se encuentra una clave coincidente.
getOrDefault
es muy agradable, pero requiere la definición predeterminada cada vez que se accede al mapa. Definir un valor predeterminado una vez también tendría beneficios de legibilidad al crear un mapa estático de valores.
private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Use el Mapa predeterminado de Commons si no tiene ganas de reinventar la rueda, por ejemplo,
Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname");
// surname == "[NO ENTRY FOUND]"
También puede pasar un mapa existente si no está a cargo de crear el mapa en primer lugar.
Java 8 introdujo un buen método predeterminado computeIfAbsent para la Map
interfaz que almacena el valor calculado de manera diferida y no rompe el contrato del mapa:
Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));
Origen: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/
Descargo de responsabilidad: esta respuesta no coincide exactamente con lo que OP solicitó, pero puede ser útil en algunos casos coincidir con el título de la pregunta cuando el número de claves es limitado y el almacenamiento en caché de diferentes valores sería rentable. No debe usarse en caso contrario con muchas teclas y el mismo valor predeterminado, ya que esto innecesariamente desperdiciaría memoria.
¿No puedes crear un método estático que haga exactamente esto?
private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
return map.containsKey(key) ? map.get(key) : defaultValue;
}
Simplemente puede crear una nueva clase que herede HashMap y agregar el método getDefault. Aquí hay un código de muestra:
public class DefaultHashMap<K,V> extends HashMap<K,V> {
public V getDefault(K key, V defaultValue) {
if (containsKey(key)) {
return get(key);
}
return defaultValue;
}
}
Creo que no debe anular el método get (clave K) en su implementación, debido a las razones especificadas por Ed Staub en su comentario y porque romperá el contrato de la interfaz de Map (esto puede conducir a algunos problemas difíciles de encontrar loco).
get
método. Por otro lado, su solución no permite usar la clase a través de la interfaz, que a menudo puede ser el caso.
Utilizar:
myHashMap.getOrDefault(key, defaultValue);
Hace esto por defecto. Vuelve null
.
HashMap
solo por esta funcionalidad cuando null
está perfectamente bien.
NullObject
embargo, no está bien si está utilizando un patrón o si no desea dispersar las verificaciones nulas en todo el código, un deseo que entiendo completamente.
El LazyMap me pareció bastante útil.
Cuando se llama al método get (Object) con una clave que no existe en el mapa, la fábrica se usa para crear el objeto. El objeto creado se agregará al mapa utilizando la clave solicitada.
Esto le permite hacer algo como esto:
Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
map.get(notExistingKey).incrementAndGet();
La llamada a get
crea un valor predeterminado para la clave dada. Usted especifica cómo crear el valor predeterminado con el argumento de fábrica para LazyMap.lazyMap(map, factory)
. En el ejemplo anterior, el mapa se inicializa a un nuevo AtomicInteger
con valor 0.
List<String>
? Al usar la respuesta aceptada, me arriesgaría a usar la misma lista para cada nueva clave, en lugar de (por ejemplo) una new ArrayList<String>()
de fábrica.
No directamente, pero puede extender la clase para modificar su método get. Aquí hay un ejemplo listo para usar: http://www.java2s.com/Code/Java/Collections-Data-Structure/ExtendedVersionofjavautilHashMapthatprovidesanextendedgetmethodaccpetingadefaultvalue.htm
/**
* Extension of TreeMap to provide default value getter/creator.
*
* NOTE: This class performs no null key or value checking.
*
* @author N David Brown
*
* @param <K> Key type
* @param <V> Value type
*/
public abstract class Hash<K, V> extends TreeMap<K, V> {
private static final long serialVersionUID = 1905150272531272505L;
/**
* Same as {@link #get(Object)} but first stores result of
* {@link #create(Object)} under given key if key doesn't exist.
*
* @param k
* @return
*/
public V getOrCreate(final K k) {
V v = get(k);
if (v == null) {
v = create(k);
put(k, v);
}
return v;
}
/**
* Same as {@link #get(Object)} but returns specified default value
* if key doesn't exist. Note that default value isn't automatically
* stored under the given key.
*
* @param k
* @param _default
* @return
*/
public V getDefault(final K k, final V _default) {
V v = get(k);
return v == null ? _default : v;
}
/**
* Creates a default value for the specified key.
*
* @param k
* @return
*/
abstract protected V create(final K k);
}
Ejemplo de uso:
protected class HashList extends Hash<String, ArrayList<String>> {
private static final long serialVersionUID = 6658900478219817746L;
@Override
public ArrayList<Short> create(Short key) {
return new ArrayList<Short>();
}
}
final HashList haystack = new HashList();
final String needle = "hide and";
haystack.getOrCreate(needle).add("seek")
System.out.println(haystack.get(needle).get(0));
Necesitaba leer los resultados devueltos por un servidor en JSON donde no podía garantizar que los campos estuvieran presentes. Estoy usando la clase org.json.simple.JSONObject que se deriva de HashMap. Aquí hay algunas funciones auxiliares que empleé:
public static String getString( final JSONObject response,
final String key )
{ return getString( response, key, "" ); }
public static String getString( final JSONObject response,
final String key, final String defVal )
{ return response.containsKey( key ) ? (String)response.get( key ) : defVal; }
public static long getLong( final JSONObject response,
final String key )
{ return getLong( response, key, 0 ); }
public static long getLong( final JSONObject response,
final String key, final long defVal )
{ return response.containsKey( key ) ? (long)response.get( key ) : defVal; }
public static float getFloat( final JSONObject response,
final String key )
{ return getFloat( response, key, 0.0f ); }
public static float getFloat( final JSONObject response,
final String key, final float defVal )
{ return response.containsKey( key ) ? (float)response.get( key ) : defVal; }
public static List<JSONObject> getList( final JSONObject response,
final String key )
{ return getList( response, key, new ArrayList<JSONObject>() ); }
public static List<JSONObject> getList( final JSONObject response,
final String key, final List<JSONObject> defVal ) {
try { return response.containsKey( key ) ? (List<JSONObject>) response.get( key ) : defVal; }
catch( ClassCastException e ) { return defVal; }
}
En proyectos mixtos de Java / Kotlin, también considere Kotlin's Map.withDefault .