Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly lazy initialize Map of Map of Map?

It may be a bad practice, but I haven't been able to figure out any better solution for my problem. So I have this map

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

and I want to initialize it so I don't get NullPointerException with this

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

I tried this one but I didn't work (obviously)

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

Other things I tried didn't compile.

Also if you have any ideas how to avoid this nested maps, I would appreciate it.

like image 890
user219882 Avatar asked Jan 30 '12 12:01

user219882


4 Answers

It seems to me that you need to create your own Key class:

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
}

If you implement your own key class, your map will look like this:

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

And when looking for something in the map you can use:

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

The method above will not throw a NullPointerException.

Please remember that for this solution to work, you need to override equals and hashcode in the Key class. There is help here. If you don't override equals and hashcode, then a new key with the same elements won't match an existing key in the map.

There are other possible solutions but implementing your own key is a pretty clean one in my opinion. If you don't want to use the constructor you can initialize your key with a static method and use something like:

Key.build(a, b, c)

It is up to you.

like image 54
Ravi Wallau Avatar answered Oct 26 '22 22:10

Ravi Wallau


You need to put maps in your maps in your map. Literally:

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

If your target is lazy initialization without NPE you have to create your own 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>();
                }
            };
        }
    };

}
like image 30
Sergey Grinev Avatar answered Oct 26 '22 22:10

Sergey Grinev


You could use a utility method:

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

This can be invoked like this:

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

Note that care is required when using this as it is not type-safe.

like image 27
McDowell Avatar answered Oct 26 '22 20:10

McDowell


The only way to do it with this structure is to pre-initialise the 1st and 2nd level maps with ALL possible keys. If this is not possible to do you can't achieve what you are asking with plain Maps.

As an alternative you can build a custom data structure that is more forgiving. For example a common trick is for a failed key lookup to return an "empty" structure rather than null, allowing nested access.

like image 38
Mike Q Avatar answered Oct 26 '22 21:10

Mike Q