Автор оригинала: Andrea Ligios.
1. Обзор
В этом уроке мы рассмотрим доступные варианты обработки Map с дубликатами ключей или, другими словами, Map , которая позволяет хранить несколько значений для одного ключа.
2. Стандартные карты
Java имеет несколько реализаций интерфейса Map , каждая из которых имеет свои особенности.
Однако ни одна из существующих реализаций карты ядра Java не позволяет Map обрабатывать несколько значений для одного ключа .
Как мы видим, если мы попытаемся вставить два значения для одного и того же ключа, второе значение будет сохранено, а первое будет удалено.
Он также будет возвращен (каждой правильной реализацией метода put(ключ K, значение V) ):
Mapmap = new HashMap<>(); assertThat(map.put("key1", "value1")).isEqualTo(null); assertThat(map.put("key1", "value2")).isEqualTo("value1"); assertThat(map.get("key1")).isEqualTo("value2");
Как же тогда мы можем достичь желаемого поведения?
3. Коллекция как ценность
Очевидно, что использование Коллекции для каждого значения нашей Карты сделает эту работу:
Map> map = new HashMap<>(); List list = new ArrayList<>(); map.put("key1", list); map.get("key1").add("value1"); map.get("key1").add("value2"); assertThat(map.get("key1").get(0)).isEqualTo("value1"); assertThat(map.get("key1").get(1)).isEqualTo("value2");
Однако это подробное решение имеет множество недостатков и подвержено ошибкам. Это означает, что нам нужно создать экземпляр Collection для каждого значения, проверить его наличие перед добавлением или удалением значения, удалить его вручную, когда не осталось значений, и т. Д.
Из Java 8 мы могли бы использовать методы compute() и улучшить его:
Map> map = new HashMap<>(); map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value1"); map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value2"); assertThat(map.get("key1").get(0)).isEqualTo("value1"); assertThat(map.get("key1").get(1)).isEqualTo("value2");
Хотя это то, что стоит знать, мы должны избегать этого, если у нас нет очень веских причин не делать этого, например, ограничительная политика компании, запрещающая нам использовать сторонние библиотеки.
В противном случае, прежде чем писать свою собственную реализацию Map и изобретать велосипед, мы должны выбрать один из нескольких вариантов, доступных из коробки.
4. Коллекции Apache Commons
Как обычно, Apache имеет решение для вашей проблемы.
Давайте начнем с импорта последней версии Common Collections (CC отныне):
org.apache.commons commons-collections4 4.1
4.1. MultiMap
org.apache.commons.collections4. MultiMap интерфейс определяет карту, которая содержит набор значений для каждого ключа.
Он реализован с помощью org.apache.commons.collections4.map. Многозначная карта класс, который автоматически обрабатывает большую часть шаблона под капотом:
MultiMapmap = new MultiValueMap<>(); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection ) map.get("key1")) .contains("value1", "value2");
Хотя этот класс доступен с CC 3.2, он не является потокобезопасным , и он устарел в CC 4.1 . Мы должны использовать его только тогда, когда мы не можем перейти на более новую версию.
4.2. Многозначная карта
Преемником MultiMap является org.apache.commons.collections4. Многозначная карта интерфейс. Он имеет несколько реализаций, готовых к использованию.
Давайте посмотрим , как сохранить наши множественные значения в ArrayList , который сохраняет дубликаты:
MultiValuedMapmap = new ArrayListValuedHashMap<>(); map.put("key1", "value1"); map.put("key1", "value2"); map.put("key1", "value2"); assertThat((Collection ) map.get("key1")) .containsExactly("value1", "value2", "value2");
В качестве альтернативы мы могли бы использовать HashSet , который отбрасывает дубликаты:
MultiValuedMapmap = new HashSetValuedHashMap<>(); map.put("key1", "value1"); map.put("key1", "value1"); assertThat((Collection ) map.get("key1")) .containsExactly("value1");
Обе вышеприведенные реализации не являются потокобезопасными .
Давайте посмотрим, как мы можем использовать UnmodifiableMultiValuedMap декоратор, чтобы сделать их неизменяемыми:
@Test(expected = UnsupportedOperationException.class) public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() { MultiValuedMapmap = new ArrayListValuedHashMap<>(); map.put("key1", "value1"); map.put("key1", "value2"); MultiValuedMap immutableMap = MultiMapUtils.unmodifiableMultiValuedMap(map); immutableMap.put("key1", "value3"); }
5. Гуава Мультимап
Guava-это основные библиотеки Google для Java API.
com.google.common.collect. Multimap интерфейс существует с версии 2. На момент написания последней версией является 25, но поскольку после версии 23 она была разделена на разные ветви для jre и android ( 25.0-jre и 25.0-android ), мы все равно будем использовать версию 23 для наших примеров.
Давайте начнем с импорта гуавы в наш проект:
com.google.guava guava 23.0
Гуава с самого начала следовал по пути многочисленных реализаций.
Наиболее распространенным из них является com.google.common.collect. ArrayListMultimap , который использует HashMap , поддерживаемый ArrayList для каждого значения:
Multimapmap = ArrayListMultimap.create(); map.put("key1", "value2"); map.put("key1", "value1"); assertThat((Collection ) map.get("key1")) .containsExactly("value2", "value1");
Как всегда, мы должны предпочесть неизменяемые реализации интерфейса Multimap: com.google.common.collect. ImmutableListMultimap и com.google.common.collect. ImmutableSetMultimap .
5.1. Общие Реализации Карт
Когда нам нужна конкретная реализация Map , первое, что нужно сделать, это проверить, существует ли она, потому что, вероятно, Гуава уже реализовала ее.
Например, мы можем использовать com.google.common.collect. LinkedHashMultimap , который сохраняет порядок вставки ключей и значений:
Multimapmap = LinkedHashMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection ) map.get("key1")) .containsExactly("value3", "value1", "value2");
В качестве альтернативы мы можем использовать com.google.common.collect. TreeMultimap , который перебирает ключи и значения в их естественном порядке:
Multimapmap = TreeMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection ) map.get("key1")) .containsExactly("value1", "value2", "value3");
5.2. Создание Нашей Пользовательской Мультикарты
Доступно множество других реализаций.
Однако мы можем захотеть украсить Карту и/или Список , еще не реализованный.
К счастью, у Гуавы есть заводской метод, позволяющий нам это сделать: Multimaps.newMultimap() .
6. Заключение
Мы видели, как хранить несколько значений для ключа на карте всеми основными существующими способами.
Мы изучили наиболее популярные реализации коллекций Apache Commons и Guava, которые по возможности следует предпочесть пользовательским решениям.
Как всегда, полный исходный код доступен на Github .