如何正确地懒惰初始化地图的地图?

时间:2012-01-30 12:08:30

标签: java collections map

这可能是一种不好的做法,但我无法为我的问题找到更好的解决方案。所以我有这张地图

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

我试过的其他事情没有编译。

此外,如果您有任何想法如何避免这种嵌套地图,我将不胜感激。

8 个答案:

答案 0 :(得分:9)

在我看来,你需要创建自己的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
}

如果您实施自己的密钥类,您的地图将如下所示:

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

在地图中查找某些内容时,您可以使用:

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

上面的方法不会抛出NullPointerException。

请记住,要使此解决方案正常工作,您需要在Key类中重写equals和hashcode。有帮助here。如果不重写equals和hashcode,则具有相同元素的新键将与映射中的现有键不匹配。

还有其他可能的解决方案,但在我看来,实现自己的密钥非常干净。如果您不想使用构造函数,可以使用静态方法初始化密钥,并使用以下内容:

Key.build(a, b, c)

由你决定。

答案 1 :(得分:5)

您需要在地图中的地图中放置地图。字面上:

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

}

答案 2 :(得分:5)

您可以使用实用程序方法:

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

请注意,使用它时需要小心,因为它不是类型安全的。

答案 3 :(得分:1)

使用此结构执行此操作的唯一方法是使用所有可能的键预初始化第1级和第2级映射。如果无法做到这一点,则无法通过普通地图实现您的要求。

作为替代方案,您可以构建更宽容的自定义数据结构。例如,一个常见的技巧是失败的键查找返回“空”结构而不是null,允许嵌套访问。

答案 4 :(得分:1)

你无法一次性初始化它,因为你通常不知道你将提前拥有什么键。

因此,您必须检查键的子图是否为空,如果是,则可能为其添加空映射。最好只在向条目添加条目时执行该操作,并且在检索条目时,如果路径中的某个子图不存在,则返回null。您可以将它包装在您自己的地图实现中,以方便使用。

作为替代方案,apache commons集合'MultiKeyMap可能会提供您想要的内容。

答案 5 :(得分:1)

使用properties.get("a").get("b").get("c");是不可能的,除非您制作自己的地图,否则请务必避免null。实际上,您无法预测您的地图将包含“b”键。 因此,请尝试创建自己的类来处理嵌套的get

答案 6 :(得分:1)

我认为更好的解决方案是使用对象作为值映射的唯一键。密钥将由三个字段statetransitionproperty组成。

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库的反射功能构建了thos方法。

答案 7 :(得分:0)

我认为,以下是更简单的方法:

public static final Map<Integer, Map<Integer, Map<Integer, Double>>> A_Map = new HashMap<Integer, Map<Integer, Map<Integer, Double>>>()
{
    {
        put(0, new HashMap<Integer, Map<Integer, Double>>()
        {
            {
                put(0, new HashMap<Integer, Double>()
                {
                    {
                        put(0, 1 / 60.0);
                        put(1, 1 / 3600.0);
                    }
                });

                put(1, new HashMap<Integer, Double>()
                {
                    {
                        put(0, 1 / 160.0);
                        put(1, 1 / 13600.0);
                    }
                });
            }
        });

        put(1, new HashMap<Integer, Map<Integer, Double>>()
        {
            {
                put(0, new HashMap<Integer, Double>()
                {
                    {
                        put(0, 1 / 260.0);
                        put(1, 1 / 3600.0);
                    }
                });

                put(1, new HashMap<Integer, Double>()
                {
                    {
                        put(0, 1 / 560.0);
                        put(1, 1 / 1300.0);
                    }
                });
            }
        });
    }
};