2012-01-30 12 views
5

Puede ser una mala práctica, pero no he podido encontrar una solución mejor para mi problema. Así que tengo este mapaCómo inicializar correctamente el Mapa de Mapa de Mapa?

// Map<state, Map<transition, Map<property, value>>> 
private Map<String, Map<String, Map<String, String>>> properties; 

y quiero inicializarlo por lo que no entiendo NullPointerException con este

properties.get("a").get("b").get("c"); 

Probé este, pero que no funcionó (obviamente)

properties = new HashMap<String, Map<String, Map<String,String>>>(); 

Otras cosas que probé no se compilaron.

Además, si tiene alguna idea sobre cómo evitar estos mapas anidados, se lo agradecería.

+0

* "Puede ser una mala práctica, pero no he podido encontrar una solución mejor para mi problema". * Tiene razón. Es casi seguro una mala práctica. Si escribió una nueva pregunta que describa el problema (específicamente los requisitos de la estructura de datos), es posible que alguien pueda sugerirle una solución mejor que usted no puede descifrar. –

+0

Gracias. Lo haré ... – user219882

Respuesta

5

Necesita colocar mapas en sus mapas en su mapa. Literalmente:

properties = new HashMap<String, Map<String, Map<String,String>>>(); 
properties.put("a", new HashMap<String, Map<String,String>>()); 
properites.get("a").put("b", new HashMap<String,String>()); 

Si su objetivo es la inicialización perezosa y sin NPE usted tiene que crear su propio mapa:

private static abstract class MyMap<K, V> extends HashMap<K, V> { 
    @Override 
    public V get(Object key) { 
     V val = super.get(key); 
     if (val == null && key instanceof K) { 
      put((K)key, val = create()); 
     } 
     return val; 
    } 

    protected abstract V create(); 
} 


public void initialize() { 
    properties = new MyMap<String, Map<String, Map<String, String>>>() { 
     @Override 
     protected Map<String, Map<String, String>> create() { 
      return new MyMap<String, Map<String, String>>() { 
       @Override 
       protected Map<String, String> create() { 
        return new HashMap<String, String>(); 
       } 
      }; 
     } 
    }; 

} 
+0

Pero no quiero poner ningún valor allí al principio. Entonces, ¿esto significa que la única solución aquí es verificar el nulo durante el acceso y, de ser así, crear el siguiente nivel? – user219882

+0

bien, he actualizado mi respuesta. –

+0

Impresionante. Gracias – user219882

1

La única manera de hacerlo con esta estructura es preinicializar los mapas de 1er y 2 ° nivel con TODAS las teclas posibles. Si esto no es posible, no puede lograr lo que está pidiendo con Mapas simples.

Como alternativa, puede crear una estructura de datos personalizada que sea más indulgente. Por ejemplo, un truco común es que una búsqueda de clave fallida devuelva una estructura "vacía" en lugar de nula, lo que permite el acceso anidado.

8

Me parece que usted necesita para crear su propia clase Key:

public class Key { 
    private final String a; 
    private final String b; 
    private final String c; 
    public Key(String a, String b, String c) { 
     // initialize all fields here 
    } 

    // you need to implement equals and hashcode. Eclipse and IntelliJ can do that for you 
} 

Si implementa su propia clase de clave, su mapa se verá así:

Map<Key, String> map = new HashMap<Key, String>(); 

Y cuando se busca algo en el mapa se puede utilizar:

map.get(new Key("a", "b", "c")); 

El método anterior no va a lanzar una NullPointerException.

Recuerde que para que esta solución funcione, debe anular equals y hashcode en la clase Key. Hay ayuda here. Si no reemplaza equals y hashcode, entonces una nueva clave con los mismos elementos no coincidirá con una clave existente en el mapa.

Existen otras soluciones posibles, pero la implementación de su propia clave es bastante limpia en mi opinión. Si no desea utilizar el constructor puede inicializar su clave con un método estático y usar algo como:

Key.build(a, b, c) 

Depende de usted.

+1

+1. He descubierto que tener clases genéricas de contenedores de pares y triples (para contener dos o tres objetos, respectivamente) es útil para este propósito, entre muchos otros ... – ach

1

No puede inicializar esto de una vez, ya que normalmente no sabe qué teclas tendrá de antemano.

Por lo tanto, debe comprobar si el submapa de una clave es nulo y, si es así, podría agregar un mapa vacío para eso.Preferiblemente, solo lo haría al agregar entradas al mapa y al recuperar las entradas devolverá nulo si uno de los submapas en la ruta no existe. Puede envolver eso en su propia implementación de mapas para facilitar su uso.

Como alternativa, las colecciones de apache commons 'MultiKeyMap pueden proporcionar lo que desee.

1

Es imposible usar properties.get("a").get("b").get("c"); y asegúrese de evitar null a menos que haga su propio Mapa. De hecho, no puede predecir que su mapa contendrá la tecla "b". Así que intente crear su propia clase para manejar el get anidado.

1

Creo que una mejor solución es usar un objeto como la única clave para el mapa de valores. La clave estará compuesta por tres campos, state, transition y property.

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

public class Key { 

    private String state; 

    private String transition; 

    private String property; 

    public Key(String state, String transition, String property) { 
     this.state = state; 
     this.transition = transition; 
     this.property = property; 
    } 

    @Override 
    public boolean equals(Object other) { 
     return EqualsBuilder.reflectionEquals(this, other); 
    } 

    @Override 
    public int hashCode() { 
     return HashCodeBuilder.reflectionHashCode(this); 
    } 

} 

Cuando busca un valor, el mapa volverá null para una llave que no está asociado con un valor

Map<Key, String> values = new HashMap<Key, String>(); 
assert values.get(new Key("a", "b", "c")) == null; 

values.put(new Key("a", "b", "c"), "value"); 
assert values.get(new Key("a", "b", "c")) != null; 
assert values.get(new Key("a", "b", "c")).equals("value"); 

Para utilizar de manera eficiente y correctamente un objeto como una llave en una Map se debe anular los métodos equals() y hashCode(). He creado estos métodos usando las funcionalidades reflexivas de la biblioteca Commons Lang.

+0

La respuesta es correcta, pero necesita un nivel adecuado (no basado en la reflexión) igual() & hashCode() implementación. Las basadas en la reflexión no son perfectas, para una clave de mapa, esto es importante, ni muestran a las personas los principios básicos para hacerlo correctamente. –

5

Se puede usar un método de utilidad:

public static <T> T get(Map<?, ?> properties, Object... keys) { 
    Map<?, ?> nestedMap = properties; 
    for (int i = 0; i < keys.length; i++) { 
     if (i == keys.length - 1) { 
     @SuppressWarnings("unchecked") 
     T value = (T) nestedMap.get(keys[i]); 
     return value; 
     } else { 
     nestedMap = (Map<?, ?>) nestedMap.get(keys[i]); 
     if(nestedMap == null) { 
      return null; 
     } 
     } 
    } 
    return null; 
    } 

Esto puede ser invocada como esto:

String result = get(properties, "a", "b", "c"); 

Tenga en cuenta que es necesario tener cuidado al usar este ya que no es de tipo seguro.

Cuestiones relacionadas