简体   繁体   English

如何正确地延迟初始化 Map 的 Map 的 Map?

[英]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我想初始化它,所以我不会得到NullPointerException

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.另外,如果您对如何避免这种嵌套地图有任何想法,我将不胜感激。

It seems to me that you need to create your own Key class:在我看来,您需要创建自己的 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
}

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.上面的方法不会抛出 NullPointerException。

Please remember that for this solution to work, you need to override equals and hashcode in the Key class.请记住,要使此解决方案起作用,您需要在 Key 类中覆盖 equals 和 hashcode。 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.如果您不覆盖 equals 和 hashcode,则具有相同元素的新键将与映射中的现有键不匹配。

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.它是由你决定。

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:如果您的目标是没有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>();
                }
            };
        }
    };

}

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.请注意,使用它时需要小心,因为它不是类型安全的。

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.例如,一个常见的技巧是失败的键查找返回一个“空”结构而不是 null,从而允许嵌套访问。

You can't initialize this in one go, since you normally don't know what keys you'll have in advance.你不能一次性初始化它,因为你通常不知道你会提前拥有什么键。

Thus you'd have to check whether the submap for a key is null and if so you might add an empty map for that.因此,您必须检查键的子图是否为空,如果是,您可能会为此添加一个空映射。 Preferably you'd only do that when adding entries to the map and upon retrieving entries you return null if one of the submaps in the path doesn't exist.最好只在向映射添加条目时这样做,并且在检索条目时,如果路径中的子映射之一不存在,则返回 null。 You could wrap that in your own map implementation for ease of use.您可以将其包装在您自己的地图实现中以方便使用。

As an alternative, apache commons collections' MultiKeyMap might provide what you want.作为替代方案,apache commons 集合的MultiKeyMap可能会提供您想要的。

It's impossible to use properties.get("a").get("b").get("c");不可能使用properties.get("a").get("b").get("c"); and be sure to avoid null unless you make your own Map.并确保避免null除非您制作自己的地图。 In fact, you can't predict that your map will contains "b" key.事实上,您无法预测您的地图将包含“b”键。 So try to make your own class to handle nested get .所以试着让你自己的类来处理嵌套的get

I think a better solution is using an object as the only key to the map of values.我认为更好的解决方案是使用对象作为值映射的唯一键。 The key will be composed of three fields, state , transition and property .密钥将由三个字段组成, 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);
    }

}

When you check for a value, the map will return null for a key that is not associated with a value当您检查一个值时,映射将为与值不关联的键返回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");

To efficiently and correctly use an object as a key in a Map you should override the methods equals() and hashCode() .为了有效且正确地使用对象作为Map中的键,您应该重写方法equals()hashCode() I have built thos methods using the reflective functionalities of the Commons Lang library.我已经使用Commons Lang库的反射功能构建了这些方法。

I think, following is the easier way:我认为,以下是更简单的方法:

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

Using computeIfAbsent/putIfAbsent makes it simple:使用 computeIfAbsent/putIfAbsent 让事情变得简单:


private <T> void addValueToMap(String keyA, String keyB, String keyC, String value) {
    map.computeIfAbsent(keyA, k -> new HashMap<>())
        .computeIfAbsent(keyB, k -> new HashMap<>())
        .putIfAbsent(keyC, value);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM