簡體   English   中英

如何正確地延遲初始化 Map 的 Map 的 Map?

[英]How to properly lazy initialize Map of Map of 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>>>();

我試過的其他東西沒有編譯。

另外,如果您對如何避免這種嵌套地圖有任何想法,我將不勝感激。

在我看來,您需要創建自己的 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。 這里有幫助。 如果您不覆蓋 equals 和 hashcode,則具有相同元素的新鍵將與映射中的現有鍵不匹配。

還有其他可能的解決方案,但我認為實現自己的密鑰是一個非常干凈的解決方案。 如果您不想使用構造函數,則可以使用靜態方法初始化密鑰並使用以下內容:

Key.build(a, b, c)

它是由你決定。

您需要將地圖放入您的地圖中。 字面上地:

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

}

您可以使用實用方法:

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

請注意,使用它時需要小心,因為它不是類型安全的。

使用此結構的唯一方法是使用所有可能的鍵預初始化第一級和第二級映射。 如果無法做到這一點,您將無法使用普通地圖實現您的要求。

作為替代方案,您可以構建更寬容的自定義數據結構。 例如,一個常見的技巧是失敗的鍵查找返回一個“空”結構而不是 null,從而允許嵌套訪問。

你不能一次性初始化它,因為你通常不知道你會提前擁有什么鍵。

因此,您必須檢查鍵的子圖是否為空,如果是,您可能會為此添加一個空映射。 最好只在向映射添加條目時這樣做,並且在檢索條目時,如果路徑中的子映射之一不存在,則返回 null。 您可以將其包裝在您自己的地圖實現中以方便使用。

作為替代方案,apache commons 集合的MultiKeyMap可能會提供您想要的。

不可能使用properties.get("a").get("b").get("c"); 並確保避免null除非您制作自己的地圖。 事實上,您無法預測您的地圖將包含“b”鍵。 所以試着讓你自己的類來處理嵌套的get

我認為更好的解決方案是使用對象作為值映射的唯一鍵。 密鑰將由三個字段組成, 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庫的反射功能構建了這些方法。

我認為,以下是更簡單的方法:

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

使用 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