2012-01-30 20 views
5

To może być zła praktyka, ale nie byłem w stanie znaleźć lepszego rozwiązania mojego problemu. Więc mam tę mapęJak poprawnie leniwy zainicjować Mapa mapy?

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

i chcę go zainicjować, więc nie dostać NullPointerException z tym

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

Próbowałem ten jeden, ale nie działa (oczywiście)

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

Inne rzeczy, których próbowałem, nie zostały skompilowane.

Również, jeśli masz jakieś pomysły, jak uniknąć tych zagnieżdżonych map, byłbym wdzięczny.

+0

* "To może być zła praktyka, ale nie byłem w stanie znaleźć lepszego rozwiązania mojego problemu." * Masz rację. To prawie na pewno zła praktyka. Jeśli napisałeś nowe pytanie nakreślające problem (w szczególności wymagania dotyczące struktury danych), ktoś może zaproponować lepsze rozwiązanie, którego nie możesz zrozumieć. –

+0

Dziękuję. Będę ... – user219882

Odpowiedz

5

Musisz umieścić mapy w swoich mapach na mapie. Dosłownie:

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

Jeśli twój cel jest leniwe inicjowanie bez NPE trzeba utworzyć własną mapę:

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

Ale nie chcę umieszczać tam żadnych wartości na początku. Oznacza to, że jedynym rozwiązaniem jest sprawdzanie wartości zerowej podczas dostępu, a jeśli tak, to tworzenie następnego poziomu? – user219882

+0

ok, zaktualizowałem swoją odpowiedź. –

+0

Awesome. Dziękuję – user219882

1

Jedynym sposobem, aby to zrobić za pomocą tej struktury, jest inicjalizacja map pierwszego i drugiego poziomu za pomocą WSZYSTKICH możliwych kluczy. Jeśli nie jest to możliwe, nie możesz osiągnąć tego, o co prosisz, korzystając ze zwykłych Map.

Jako alternatywę można zbudować niestandardową strukturę danych, która jest bardziej wyrozumiała. Na przykład typowa sztuczka polega na tym, że nieudane wyszukiwanie klucza zwróci "pustą" strukturę zamiast wartości NULL, umożliwiając zagnieżdżony dostęp.

8

Wydaje mi się, że trzeba utworzyć własną klasę 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 
} 

Jeśli zaimplementujesz własną klasę kluczy, Twoja mapa będzie wyglądać następująco:

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

A jeśli szukasz czegoś na mapie można użyć:

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

Powyższa metoda nie będzie rzucać NullPointerException.

Należy pamiętać, że aby to rozwiązanie działało, należy zastąpić equals i hashcode w klasie Key. Jest pomoc here. Jeśli nie zastąpisz equals i hashcode, nowy klucz z tymi samymi elementami nie będzie pasował do istniejącego klucza na mapie.

Istnieją inne możliwe rozwiązania, ale moim zdaniem wdrożenie własnego klucza jest całkiem czyste. Jeśli nie chcesz korzystać z konstruktora można zainicjować klucz z metodą statyczną i używać coś takiego:

Key.build(a, b, c) 

To do ciebie.

+1

+1. Odkryłem, że posiadanie klasycznych klas kontenerów Pair i Triple (dla przechowywania odpowiednio dwóch lub trzech obiektów) jest przydatne w tym celu, wśród wielu innych ... – ach

1

Nie można zainicjować tego za jednym razem, ponieważ zwykle nie wiesz, jakie klucze będziesz mieć z góry.

W związku z tym należy sprawdzić, czy submap dla klucza ma wartość NULL, a jeśli tak, to może dodać pustą mapę.Najlepiej byłoby to zrobić tylko przy dodawaniu wpisów do mapy, a po odebraniu wpisów zwracano wartość null, jeśli jeden z podtapów w ścieżce nie istnieje. Można to zawijać we własnej implementacji mapy dla łatwości użycia.

Jako alternatywę, kolekcje Apache commons "MultiKeyMap mogą dostarczać to, co chcesz.

1

Nie można używać properties.get("a").get("b").get("c"); i należy unikać null, chyba że stworzysz własną mapę. W rzeczywistości nie można przewidzieć, że mapa będzie zawierała klucz "b". Więc spróbuj stworzyć własną klasę do obsługi zagnieżdżonych get.

1

Myślę, że lepszym rozwiązaniem jest użycie obiektu jako jedynego klucza do mapy wartości. Klucz będzie składał się z trzech pól: state, transition i 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); 
    } 

} 

Podczas sprawdzania wartości, mapa powróci null o klucz, który nie jest związany z wartością

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

Aby efektywnie i prawidłowo wykorzystywać obiekt jako klucz w Map ty powinien zastąpić metody. Zbudowałem te metody, używając funkcjonalności odblaskowych biblioteki Commons Lang.

+0

Odpowiedź jest OK, ale wymaga odpowiedniego (nie opartego na refleksji)() i implementacja hashCode(). Oparte na odbiciu nie są wydajne - dla klucza mapy jest to ważne - ani nie pokazują ludziom podstawowych zasad, aby zrobić to poprawnie. –

5

Można użyć metody użytkowy:

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

ta może być wywołana tak:

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

Należy pamiętać, że opieka jest potrzebne do korzystania z tego, jak to nie jest typ bezpieczny.