Java中的嵌套HashMap示例

2023/06/07

1. 概述

在本教程中,我们将了解如何在Java中处理嵌套的HashMap。我们将了解如何创建和比较它们。最后,我们还将了解如何在内部Map中删除和添加记录。

2. 用例

嵌套HashMap非常有助于存储JSON或类似JSON的结构,其中对象相互嵌入。例如,类似于以下内容的结构或JSON:

{
    "type": "donut",
    "batters":
    {
        "batter":
        [
            { "id": "1001", "type": "Regular" },
            { "id": "1002", "type": "Chocolate" },
            { "id": "1003", "type": "Blueberry" },
            { "id": "1004", "type": "Devil's Food" }
        ]
    }
} 

是嵌套HashMap的完美候选者。一般来说,只要我们需要将一个对象嵌入到另一个对象中,我们就可以使用它们。

3. 创建一个HashMap

有多种创建HashMap的方法,例如手动构建Map或使用Streams和分组函数。Map结构既可以是原始类型,也可以是Object

3.1 使用put()方法

我们可以通过手动创建内部Map然后使用put方法将它们插入外部Map来构建嵌套的HashMap:

public Map<Integer, String> buildInnerMap(List<String> batterList) {
     Map<Integer, String> innerBatterMap = new HashMap<Integer, String>();
     int index = 1;
     for (String item : batterList) {
         innerBatterMap.put(index, item);
         index++;
     }
     return innerBatterMap;
}

我们可以通过以下方式进行测试:

assertThat(mUtil.buildInnerMap(batterList), is(notNullValue()));
Assert.assertEquals(actualBakedGoodsMap.keySet().size(), 2);
Assert.assertThat(actualBakedGoodsMap, IsMapContaining.hasValue(equalTo(mUtil.buildInnerMap(batterList))));

3.2 使用Stream

如果我们有一个要转换为Map的List,我们可以创建一个流,然后使用Collectors.toMap方法将其转换为Map。在这里,我们有两个示例:一个是字符串的内部Map,另一个是具有Integer和Object值的Map。

在第一个示例中,Employee中嵌套了Address对象。然后我们构建一个嵌套的HashMap:

Map<Integer, Map<String, String>> employeeAddressMap = listEmployee.stream()
    .collect(Collectors.groupingBy(e -> e.getAddress().getAddressId(),
        Collectors.toMap(f -> f.getAddress().getAddressLocation(), Employee::getEmployeeName)));
return employeeAddressMap;

在第二个示例中,我们正在构建一个类型为<Employee id <Address id, Address object>>的对象:

Map<Integer, Map<Integer, Address>> employeeMap = new HashMap<>();
employeeMap = listEmployee.stream().collect(Collectors.groupingBy((Employee emp) -> emp.getEmployeeId(),
    Collectors.toMap((Employee emp) -> emp.getAddress().getAddressId(), fEmpObj -> fEmpObj.getAddress())));
return employeeMap;

4. 遍历嵌套的HashMap

遍历嵌套的Hashmap与遍历常规或非嵌套的HashMap没有什么不同。嵌套和常规Map之间的唯一区别是嵌套HashMap的值是Map类型:

for (Map.Entry<String, Map<Integer, String>> outerBakedGoodsMapEntrySet : outerBakedGoodsMap.entrySet()) {
    Map<Integer, String> valueMap = outerBakedGoodsMapEntrySet.getValue();
    System.out.println(valueMap.entrySet());
}

for (Map.Entry<Integer, Map<String, String>> employeeEntrySet : employeeAddressMap.entrySet()) {
    Map<String, String> valueMap = employeeEntrySet.getValue();
    System.out.println(valueMap.entrySet());
}

5. 比较嵌套的HashMap

在Java中有很多方法可以比较HashMap。我们可以使用equals()方法比较它们,默认实现比较每个值。

如果我们更改内部Map的内容,相等性检查将失败。对于自定义对象,如果每次内部对象都是新实例,相等性检查也会失败。同样,如果我们更改外部Map的内容,相等性检查也会失败:

assertNotEquals(outerBakedGoodsMap2, actualBakedGoodsMap);

outerBakedGoodsMap3.put("Donut", mUtil.buildInnerMap(batterList));
assertNotEquals(outerBakedGoodsMap2, actualBakedGoodsMap);

Map<Integer, Map<String, String>> employeeAddressMap1 = mUtil.createNestedMapfromStream(listEmployee);
assertNotEquals(employeeAddressMap1, actualEmployeeAddressMap);

对于以自定义对象为值的Map,我们需要使用比较HashMap一文中提到的方法之一自定义equals方法。否则,检查将失败:

//Comparing a Map<Integer, Map<String, String>> and Map<Integer, Map<Integer, Address>> map
assertNotSame(employeeMap1, actualEmployeeMap);
assertNotEquals(employeeMap1, actualEmployeeMap);
Map<Integer, Map<Integer, Address>> expectedMap = setupAddressObjectMap();
assertNotSame(expectedMap, actualEmployeeMap);
assertNotEquals(expectedMap, actualEmployeeMap);

如果两个Map相同,则相等性检查成功。对于用户定义的Map,如果所有相同的对象都移动到另一个Map中,则相等性检查成功:

Map<String, Map<Integer, String>> outerBakedGoodsMap4 = new HashMap<>();
outerBakedGoodsMap4.putAll(actualBakedGoodsMap);
assertEquals(actualBakedGoodsMap, outerBakedGoodsMap4);
Map<Integer, Map<Integer, Address>> employeeMap1 = new HashMap<>();
employeeMap1.putAll(actualEmployeeMap);
assertEquals(actualEmployeeMap, employeeMap1);

6. 向嵌套的HashMap添加元素

要将元素添加到嵌套HashMap的内部Map中,我们首先必须检索它。我们可以使用get()方法检索内部对象。然后我们可以在内部Map对象上使用put()方法并插入新值:

assertEquals(actualBakedGoodsMap.get("Cake").size(), 5);
actualBakedGoodsMap.get("Cake").put(6, "Cranberry");
assertEquals(actualBakedGoodsMap.get("Cake").size(), 6);

如果我们必须向外部Map添加一个条目,我们还需要为内部Map提供正确的条目:

outerBakedGoodsMap.put("Eclair", new HashMap<Integer, String>() {
    {
        put(1, "Dark Chocolate");
    }
});

7. 从嵌套的HashMap中删除记录

要从内部Map中删除记录,首先,我们需要检索它,然后使用remove()方法将其删除。如果内部Map中只有一个值,则保留一个空对象作为值:

assertNotEquals(actualBakedGoodsMap.get("Cake").get(5), null);
actualBakedGoodsMap.get("Cake").remove(5);
assertEquals(actualBakedGoodsMap.get("Cake").get(5), null);
assertNotEquals(actualBakedGoodsMap.get("Eclair").get(1), null);
actualBakedGoodsMap.get("Eclair").remove(1);
assertEquals(actualBakedGoodsMap.get("Eclair").get(1), null);
actualBakedGoodsMap.put("Eclair", new HashMap<Integer, String>() {
    {
        put(1, "Dark Chocolate");
    }
});

如果我们从外部Map中删除一条记录,Java会同时删除内部和外部Map记录,这是显而易见的,因为内部Map是外部Map的“值”:

assertNotEquals(actualBakedGoodsMap.get("Eclair"), null);
actualBakedGoodsMap.remove("Eclair");
assertEquals(actualBakedGoodsMap.get("Eclair"), null);

8. 展平嵌套的HashMap

嵌套HashMap的一种替代方法是使用组合键。组合键通常将嵌套结构中的两个键与中间的点拼接起来。例如,组合键将是Donut.1、Donut.2等等。我们可以“扁平化”,即从嵌套的Map结构转换为单个Map结构:

var flattenedBakedGoodsMap = mUtil.flattenMap(actualBakedGoodsMap);
assertThat(flattenedBakedGoodsMap, IsMapContaining.hasKey("Donut.2"));
var flattenedEmployeeAddressMap = mUtil.flattenMap(actualEmployeeAddressMap);
assertThat(flattenedEmployeeAddressMap, IsMapContaining.hasKey("200.Bag End"));

组合键方法克服了嵌套HashMap带来的额外内存存储缺点。但是,组合键方法在缩放方面不是很好。

9. 总结

在本文中,我们了解了如何创建、比较、更新和展平嵌套的HashMap。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章