2012-01-30 1 views
5

Potrebbe essere una cattiva pratica, ma non sono stato in grado di trovare una soluzione migliore per il mio problema. Così ho questa mappaCome inizializzare correttamente pigro Mappa della mappa della mappa?

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

e voglio inizializzare quindi non ottengo NullPointerException con questo

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

ho provato questo, ma non ho lavorato (ovviamente)

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

Altre cose che ho provato non sono state compilate.

Anche se avete qualche idea su come evitare queste mappe nidificate, lo apprezzerei.

+0

* "Potrebbe essere una cattiva pratica, ma non sono stato in grado di trovare una soluzione migliore per il mio problema." * Hai ragione. È quasi certamente una cattiva pratica. Se hai scritto una nuova domanda che descrive il problema (in particolare i requisiti della struttura dei dati), qualcuno potrebbe essere in grado di suggerire una soluzione migliore che non riesci a capire. –

+0

Grazie. Lo farò ... – user219882

risposta

5

È necessario inserire mappe nelle mappe nella mappa. Letteralmente:

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>()); 

Se il tuo obiettivo è l'inizializzazione differita senza NPE è necessario creare la tua mappa:

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

Ma non voglio mettere alcun valore lì all'inizio. Quindi questo significa che l'unica soluzione qui è quella di verificare la presenza di null durante l'accesso e, in tal caso, creare il livello successivo? – user219882

+0

ok, ho aggiornato la mia risposta. –

+0

Fantastico. Grazie – user219882

1

L'unico modo per farlo con questa struttura è pre-inizializzare le mappe di primo e secondo livello con TUTTE le chiavi possibili. Se ciò non è possibile, non puoi ottenere ciò che stai chiedendo con Mappe semplici.

In alternativa è possibile creare una struttura dati personalizzata più indulgente. Ad esempio, un trucco comune è che una ricerca di chiavi fallita restituisca una struttura "vuota" anziché null, consentendo l'accesso annidato.

8

Mi sembra che è necessario creare la propria classe chiave:

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 
} 

Se si implementa la propria classe chiave, la mappa sarà simile a questa:

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

E quando alla ricerca di qualcosa nella mappa è possibile utilizzare:

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

Il metodo di cui sopra non gettare un NullPointerException.

Si ricorda che per questa soluzione, è necessario eseguire l'override di equals e hashcode nella classe Key. C'è aiuto here. Se non si esegue l'override di equals e hashcode, una nuova chiave con gli stessi elementi non corrisponderà a una chiave esistente nella mappa.

Ci sono altre possibili soluzioni, ma l'implementazione della propria chiave è piuttosto pulita a mio parere. Se non si desidera utilizzare il costruttore è possibile inizializzare la chiave con un metodo statico e usare qualcosa come:

Key.build(a, b, c) 

Spetta a voi.

+1

+1. Ho trovato che avere classi di contenitori Pair e Triple generici (per contenere due o tre oggetti, rispettivamente) è utile per questo scopo, tra molti altri ... – ach

1

Non è possibile inizializzare questo in una volta, poiché normalmente non si conoscono le chiavi che si avranno in anticipo.

Pertanto, è necessario verificare se il submap per una chiave è nullo e in tal caso è possibile che aggiunga una mappa vuota per quello.Preferibilmente lo faresti solo quando aggiungi delle voci alla mappa e al momento del recupero delle voci restituisci null se uno dei submap nel percorso non esiste. Potresti avvolgerlo nella tua implementazione della mappa per facilità d'uso.

In alternativa, le raccolte di apache commons 'MultiKeyMap potrebbero fornire ciò che si desidera.

1

È impossibile utilizzare properties.get("a").get("b").get("c"); e assicurarsi di evitare null a meno che non si crei la propria mappa. In realtà, non puoi prevedere che la tua mappa contenga il tasto "b". Quindi prova a creare la tua classe per gestire nidificato get.

1

Penso che una soluzione migliore stia usando un oggetto come unica chiave per la mappa dei valori. La chiave sarà composta da tre campi, state, transition e 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); 
    } 

} 

Quando si controlla per un valore, la mappa tornerà null per una chiave che non è associato con un valore

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"); 

Per utilizzare in modo efficiente e corretto un oggetto come una chiave in un Map voi dovrebbe ignorare i metodi equals() e hashCode(). Ho costruito questi metodi utilizzando le funzionalità riflettenti della libreria Commons Lang.

+0

La risposta è OK, ma ha bisogno di un corretto (non basato sulla riflessione) uguale() e implementazione hashCode(). Quelli basati sulla riflessione non sono né performanti - per una chiave mappa, questo è importante - né mostrano alle persone i principi di base per farlo correttamente. –

5

è possibile utilizzare un metodo di utilità:

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

Questo può essere invocato in questo modo:

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

Nota che la cura è necessaria quando si utilizza questo in quanto non è il tipo di sicurezza.

Problemi correlati