1. Обзор
В этом руководстве мы рассмотрим, как работать с вложенными HashMaps
в Java. Мы также увидим, как создавать и сравнивать их. Наконец, мы также увидим, как удалять и добавлять записи на внутренние карты.
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
, например, создание карт вручную или использование потоков
и функций группировки. Структура карты
может быть как с примитивными типами, так и с объектами
.
3.1. Использование метода put
()
Мы можем создать вложенную HashMap
, вручную создав внутренние карты, а затем вставив их во внешнюю карту
с помощью метода put:
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. Использование потоков
Если у нас есть список
, который мы хотим преобразовать в карту
, мы можем создать поток, а затем преобразовать его в карту
с помощью метода Collectors.toMap
. Здесь у нас есть два примера: один имеет внутреннюю карту
строк , а
другой — карту
со значениями Integer
и Object
.
В первом примере в объект 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
. Единственная разница между вложенной и обычной картой
заключается в том, что значения вложенной карты HashMap
относятся к типу карты
:
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
Есть много способов сравнить HashMap
в Java. Мы можем сравнить их, используя метод equals()
. Реализация по умолчанию сравнивает каждое значение.
Если мы изменим содержимое внутренней карты, проверка на равенство завершится неудачно. Если все внутренние объекты каждый раз являются новыми экземплярами в случае пользовательских объектов, проверка на равенство также завершится ошибкой. Точно так же, если мы изменим содержимое внешней карты 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);
Для Карты
с пользовательскими объектами в качестве значений нам нужно настроить метод равенства, используя один из методов, упомянутых в статье сравнения HashMap
. В противном случае проверки не пройдут:
//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<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
, нам сначала нужно его получить. Мы можем получить внутренний объект, используя метод get()
. Затем мы можем использовать метод put()
для внутреннего объекта Map
и вставить новые значения:
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
Чтобы удалить запись из внутренней карты
, сначала нам нужно получить ее, а затем использовать метод 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");
}
});
Если мы удалим запись из внешней карты
, Java удалит как внутреннюю, так и внешнюю записи карты
, что очевидно, поскольку внутренняя карта
является «значением» внешней карты
:
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"));
Подход с комбинированными ключами устраняет недостатки дополнительного хранения памяти, связанные с вложенными HashMaps
. Однако подход с комбинированными ключами не очень хорош для масштабирования.
9. Заключение
В этой статье мы увидели, как создавать, сравнивать, обновлять и сглаживать вложенную HashMap
.
Как всегда, код доступен на GitHub .