2011-01-28 8 views
32

Tengo un Map<String, List<String>> en mi código, donde evitaría posibles punteros nulos si el método #get() del mapa devolviera una lista vacía en lugar de nulo. ¿Hay algo como esto en la API de Java? ¿Debo simplemente extender HashMap?Implementación del mapa Java que devuelve un valor predeterminado en lugar de nulo

+7

utilizando el patrón decorador, probablemente sería mejor que HashMap se extiende ... – kem

+1

No le cabe duda, pero ¿por qué sería mejor? –

+7

@jk: ¿Por qué querría limitarse a él * siempre * siendo un HashMap? ¿Qué pasa si a veces quieres un LinkedHashMap? ¿O un ConcurrentHashMap? O un TreeMap? Básicamente, favorecer la composición sobre la herencia :) –

Respuesta

23

@ La respuesta de Jon es una buena manera de hacer lo que está preguntando directamente.

Pero me sorprende que lo que intentas implementar es un "multimap"; es decir, un mapeo de una clave a una colección de valores. Si ese es el caso, entonces también debería mirar las clases multimapa en las colecciones de Guava o Apache commons.

Mira:

+4

+1 Y 'get()' en un 'Multimap' que no contiene la clave devuelve una' Colección' vacía por contrato ... exactamente lo que quiere el OP. Lo mismo no sucede con Apache Commons 'MultiMap' que tiene un comportamiento no especificado. – ColinD

+2

@ColinD cierto. Al usar apache commons, podemos usar DefaultedMap, que devuelve un valor predeterminado cuando el mapa no contiene el valor especificado. – simao

24

Como se señala en los comentarios:

mapa de computación concepto de guayaba fue reemplazado con LoadingCache. También se introdujo Java 8 en la interfaz de Map nice computeIfAbsent método predeterminado que no rompe el contrato del mapa y cuenta con evaluación diferida.


Guava tuvieron la idea de una "hoja de cálculo" que ejecutará una función para proporcionar un valor si no está presente. Se implementó en MapMaker.makeComputingMap; ahora puede usar CacheBuilder - vea CacheBuilder.build para más detalles.

Puede ser excesiva para lo que está buscando - que podría ser mejor simplemente escribiendo una aplicación Map cuales tiene un Map (usando la composición en lugar de extender cualquier aplicación particular) y luego simplemente devolver el valor predeterminado si no es presente. Cada método distinto get probablemente podría simplemente delegar en el otro mapa:

public class DefaultingMap<K, V> implements Map<K, V> 
{ 
    private final Map<K, V> map; 
    private final V defaultValue; 

    public DefaultingMap(Map<K, V> map, V defaultValue) 
    { 
     this.map = map; 
     this.defaultValue = defaultValue; 
    } 

    @Override public V get(Object key) 
    { 
     V ret = map.get(key); 
     if (ret == null) 
     { 
      ret = defaultValue; 
     } 
     return ret; 
    } 

    @Override public int size() 
    { 
     return map.size(); 
    } 

    // etc 
} 
+4

Cuidado: al hacer esto se rompe el contrato de 'get()': si el mapa no contiene la clave, 'null' se devuelve. Para cumplir, debe anular 'containsKey()' a * always * return 'true'. Asumiría que 'makeComputingMap()' también lo haría, pero no lo encontró explícitamente en los documentos. –

+3

@Mark: Posiblemente. Aunque luego te metes en la dificultad de iterar sobre las teclas, también ... básicamente tarde o temprano la abstracción se rompe. * Donde * lo rompa definitivamente sería una cuestión de profunda consideración :) –

+1

El '// etc' no es pequeño, y hace que esta solución impráctica IMO. –

0

Me parece que simplemente puede escribir una función de contenedor para obtener el valor que desea y si es null retorno por defecto en su lugar.

Map<String, List<String>> myMap = new Map<String, List<String>>(); 

public List<String> myGet(String key) { 
    List<String> ret = myMap.get(key); 
    if(ret == NULL) { 
     return defaultList(); // where defaultList is a function that generates your default list. 
    } 
    return ret; 
} 

Esto supone que su myMap es una variable global (para simplificar), si eso no es lo que quieres, entonces también se puede extender o MapHashMap en algo así como MyMap y basta con incluir esta función adicional. De esta forma, puede usarlo desde cualquier lugar donde se instancia un objeto MyMap.

+0

Hmm, el patrón de decorador mencionado anteriormente parece una idea aún mejor ... – Argote

+2

Esto no ayuda mucho si necesita pasar el mapa a algo esperando un Mapa. –

8

Similar a las publicaciones anteriores, excepto que puede anular el método get si desea cambiar su comportamiento.

Map<String, List<String>> map = new LinkedHashMap<String, List<String>>() { 
    public String get(Object key) { 
     List<String> list = super.get(key); 
     if (list == null && key instanceof String) 
      super.put(key, list = new ArrayList<String>()); 
     return list; 
    } 
}; 
7

Guava tiene un método para hacer exactamente lo que usted desea.Es similar al Argote's answer.

Map<String, List<String>> myMap = ... 
Functions.forMap(myMap, Arrays.asList("default", "value")); 
+0

Very nice one-liner. : D – epicrose

+2

Tal vez me falta algo, pero ¿cómo crea esto una implementación 'Map' que tiene un valor predeterminado? – ach

+1

No es así. Crea una 'Función' de Guava, que es similar a un 'Mapa'. La interfaz 'Map' no permite valores predeterminados. –

25

Gracias a default métodos, Java 8 ahora tiene esta construido con Map::getOrDefault:

Map<Integer, String> map = ... 
map.put(1, "1"); 
System.out.println(map.getOrDefault(1, "2")); // "1" 
System.out.println(map.getOrDefault(2, "2")); // "2" 
+2

Esta es la respuesta más delgada ya que no requiere guayaba. Y, por cierto, era lo que estaba buscando. Voto ascendente. –

+2

Parece que según el momento esta es la mejor solución, pero llegó muy tarde a la fiesta (¡gracias java 8!). Me gusta su simplicidad. +1 –

Cuestiones relacionadas