Как правильно лениво инициализировать Map of Map? - PullRequest
5 голосов
/ 30 января 2012

Это может быть плохая практика, но я не смог найти лучшего решения для моей проблемы.Итак, у меня есть эта карта

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

, и я хочу ее инициализировать, чтобы я не получил NullPointerException с этим

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

Я пробовал эту карту, но у меня не получилось(очевидно)

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

Другие вещи, которые я пробовал, не компилировались.

Также, если у вас есть идеи, как избежать вложенных карт, я был бы признателен.

Ответы [ 7 ]

9 голосов
/ 30 января 2012

Мне кажется, вам нужно создать свой собственный класс ключей:

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
}

Если вы реализуете свой собственный класс ключей, ваша карта будет выглядеть так:

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

И когда вы ищете что-то на карте, вы можете использовать:

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

Приведенный выше метод не вызовет исключение NullPointerException.

Пожалуйста, помните, что для работы этого решения вам необходимо переопределить равныеи хэш-код в классе Key.Здесь есть помощь .Если вы не переопределите equals и hashcode, то новый ключ с такими же элементами не будет соответствовать существующему ключу на карте.

Существуют другие возможные решения, но реализация вашего собственного ключа довольно чистая.по-моему.Если вы не хотите использовать конструктор, вы можете инициализировать свой ключ статическим методом и использовать что-то вроде:

Key.build(a, b, c)

Это ваше дело.

5 голосов
/ 30 января 2012

Вы можете использовать служебный метод:

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

Это может быть вызвано так:

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

Обратите внимание, что при использовании этого метода требуется осторожность, так как он не является безопасным для типов.

5 голосов
/ 30 января 2012

Вы должны поместить карты в свои карты на вашей карте.Буквально:

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

Если ваша цель - ленивая инициализация без NPE, вы должны создать свою собственную карту:

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

}
1 голос
/ 30 января 2012

Я думаю, что лучшим решением будет использование объекта в качестве единственного ключа к карте значений.Ключ будет состоять из трех полей: state, transition и 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);
    }

}

Когда вы проверите значение, карта вернет null для ключа, который не являетсясвязанный со значением

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

Чтобы эффективно и правильно использовать объект в качестве ключа в Map, необходимо переопределить методы equals() и hashCode().Я создал эти методы, используя отражающие функции библиотеки Commons Lang .

1 голос
/ 30 января 2012

Невозможно использовать properties.get("a").get("b").get("c"); и обязательно избегать null, если вы не создадите свою собственную карту.На самом деле, вы не можете предсказать, что ваша карта будет содержать ключ «b».Поэтому постарайтесь создать свой собственный класс для обработки вложенных get.

1 голос
/ 30 января 2012

Вы не можете инициализировать это за один раз, так как вы обычно не знаете, какие ключи у вас будут заранее.

Таким образом, вам нужно проверить, является ли подкарта для ключа нулевой, и если это так, вы могли бы добавить для этого пустую карту. Желательно, чтобы вы делали это только при добавлении записей на карту и при извлечении записей, вы возвращаете пустое значение, если одно из вложенных карт в пути не существует. Вы можете обернуть это в свою собственную реализацию карты для простоты использования.

В качестве альтернативы apache commons collection ' MultiKeyMap может предоставить то, что вы хотите.

1 голос
/ 30 января 2012

Единственный способ сделать это с этой структурой - предварительно инициализировать карты 1-го и 2-го уровня ВСЕМИ возможными ключами.Если это невозможно, вы не можете достичь того, о чем просите, с помощью простых карт.

В качестве альтернативы вы можете создать собственную структуру данных, которая будет более щадящей.Например, обычная хитрость заключается в том, что при неудачном поиске по ключу возвращается «пустая» структура, а не ноль, что позволяет использовать вложенный доступ.

...