← Назад к вопросам

Как хранятся несколько значений с одним ключом

1.3 Junior🔥 71 комментариев
#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Хранение нескольких значений с одним ключом в Java

Это практическая задача, которая часто возникает в разработке. Есть несколько стандартных подходов, каждый со своими преимуществами.

Проблема

// ❌ Проблема: обычная Map хранит только одно значение на ключ
Map<String, String> map = new HashMap<>();
map.put("tags", "java");
map.put("tags", "spring");  // Перезаписывает предыдущее значение

System.out.println(map.get("tags"));  // Выведет только "spring"

Решение 1: Map со списком значений (КЛАССИЧЕСКИЙ ПОДХОД)

Самый простой и понятный способ — использовать Map<K, List<V>>:

public class MultiValueMap {
    
    private Map<String, List<String>> map = new HashMap<>();
    
    public void put(String key, String value) {
        // Если ключа нет, создаём новый список
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }
    
    public List<String> get(String key) {
        return map.getOrDefault(key, Collections.emptyList());
    }
    
    public void remove(String key) {
        map.remove(key);
    }
    
    public void removeValue(String key, String value) {
        List<String> values = map.get(key);
        if (values != null) {
            values.remove(value);
            // Если список пуст, удаляем ключ
            if (values.isEmpty()) {
                map.remove(key);
            }
        }
    }
}

// Использование
MultiValueMap map = new MultiValueMap();
map.put("tags", "java");
map.put("tags", "spring");
map.put("tags", "hibernate");

List<String> tags = map.get("tags");
System.out.println(tags);  // [java, spring, hibernate]

Решение 2: Apache Commons MultiValueMap

Апач предоставляет готовую реализацию:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

public class MultiValueMapExample {
    public static void main(String[] args) {
        MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();
        
        map.put("tags", "java");
        map.put("tags", "spring");
        map.put("tags", "hibernate");
        
        // Получить все значения
        Collection<String> tags = map.get("tags");
        System.out.println(tags);  // [java, spring, hibernate]
        
        // Размер всех значений для ключа
        int count = map.size();  // 3
        
        // Содержит ли значение
        boolean contains = map.containsValue("spring");  // true
        
        // Удалить конкретное значение
        map.removeMapping("tags", "spring");
    }
}

Решение 3: Guava Multimap

Google Guava также предоставляет элегантное решение:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
</dependency>
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

public class GuavaMultimapExample {
    public static void main(String[] args) {
        // Мутабельный multimap
        Multimap<String, String> map = ArrayListMultimap.create();
        
        map.put("tags", "java");
        map.put("tags", "spring");
        map.put("tags", "hibernate");
        
        // Все значения
        Collection<String> tags = map.get("tags");
        System.out.println(tags);  // [java, spring, hibernate]
        
        // Количество значений для ключа
        int size = map.size();  // 3
        
        // Вспомогательные операции
        System.out.println(map.keys());      // Multiset [tags, tags, tags]
        System.out.println(map.values());    // Collection [java, spring, hibernate]
        System.out.println(map.entries());   // Entries
        
        // Удалить конкретное значение
        map.remove("tags", "spring");
    }
}

Иммутабельный Multimap:

import com.google.common.collect.ImmutableListMultimap;

ImmutableListMultimap<String, String> map = 
    ImmutableListMultimap.<String, String>builder()
        .put("tags", "java")
        .put("tags", "spring")
        .put("tags", "hibernate")
        .build();

// Создание из обычного map
Map<String, String> data = new HashMap<>();
ImmutableListMultimap<String, String> immutable = 
    Multimaps.index(data.entrySet(), Map.Entry::getKey);

Решение 4: Map<K, Set<V>> (без дубликатов)

Если не нужны дубликаты, используем Set:

public class MultiValueSetMap {
    
    private Map<String, Set<String>> map = new HashMap<>();
    
    public void put(String key, String value) {
        map.computeIfAbsent(key, k -> new HashSet<>()).add(value);
    }
    
    public Set<String> get(String key) {
        return map.getOrDefault(key, Collections.emptySet());
    }
    
    public boolean contains(String key, String value) {
        Set<String> values = map.get(key);
        return values != null && values.contains(value);
    }
}

// Использование
MultiValueSetMap map = new MultiValueSetMap();
map.put("tags", "java");
map.put("tags", "java");  // Не добавится (дубликат)
map.put("tags", "spring");

Set<String> tags = map.get("tags");
System.out.println(tags);  // [java, spring] (без дубликатов)

Решение 5: Map<K, Set<V>> с TreeSet (сортировка)

Для упорядоченных значений:

public class SortedMultiValueMap {
    
    private Map<String, Set<String>> map = new HashMap<>();
    
    public void put(String key, String value) {
        // Используем TreeSet для автоматической сортировки
        map.computeIfAbsent(key, k -> new TreeSet<>()).add(value);
    }
    
    public Set<String> get(String key) {
        return map.getOrDefault(key, Collections.emptySortedSet());
    }
}

// Использование
SortedMultiValueMap map = new SortedMultiValueMap();
map.put("tags", "spring");
map.put("tags", "java");
map.put("tags", "hibernate");

Set<String> tags = map.get("tags");
System.out.println(tags);  // [hibernate, java, spring] - отсортировано

Решение 6: Использование Stream API для трансформации

Трансформирование коллекции в multimap:

import java.util.stream.Collectors;

public class StreamToMultimap {
    public static void main(String[] args) {
        // Список пользователей
        List<User> users = Arrays.asList(
            new User("john", "admin"),
            new User("jane", "user"),
            new User("bob", "admin"),
            new User("alice", "user")
        );
        
        // Группируем по роли с использованием Collectors.groupingBy
        Map<String, List<User>> usersByRole = users.stream()
            .collect(Collectors.groupingBy(User::getRole));
        
        System.out.println(usersByRole);
        // {admin=[john, bob], user=[jane, alice]}
        
        // Группируем и трансформируем значения
        Map<String, List<String>> namesByRole = users.stream()
            .collect(Collectors.groupingBy(
                User::getRole,
                Collectors.mapping(User::getName, Collectors.toList())
            ));
        
        System.out.println(namesByRole);
        // {admin=[john, bob], user=[jane, alice]}
        
        // Группируем в Set (без дубликатов)
        Map<String, Set<String>> uniqueNamesByRole = users.stream()
            .collect(Collectors.groupingBy(
                User::getRole,
                Collectors.mapping(User::getName, Collectors.toSet())
            ));
    }
    
    static class User {
        String name;
        String role;
        User(String name, String role) { this.name = name; this.role = role; }
        String getName() { return name; }
        String getRole() { return role; }
    }
}

Решение 7: Использование groupingByConcurrent для параллельной обработки

import java.util.stream.Collectors;

Map<String, List<User>> usersByRole = users.parallelStream()
    .collect(Collectors.groupingByConcurrent(
        User::getRole,
        Collectors.toList()
    ));

Решение 8: Собственная реализация с пользовательскими операциями

public class CustomMultiMap<K, V> {
    
    private Map<K, List<V>> map = new HashMap<>();
    
    public void put(K key, V value) {
        map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }
    
    public void putAll(K key, Collection<V> values) {
        map.computeIfAbsent(key, k -> new ArrayList<>()).addAll(values);
    }
    
    public List<V> get(K key) {
        return map.getOrDefault(key, Collections.emptyList());
    }
    
    public void remove(K key) {
        map.remove(key);
    }
    
    public void removeValue(K key, V value) {
        List<V> values = map.get(key);
        if (values != null && values.remove(value)) {
            if (values.isEmpty()) {
                map.remove(key);
            }
        }
    }
    
    public boolean contains(K key, V value) {
        List<V> values = map.get(key);
        return values != null && values.contains(value);
    }
    
    public int size(K key) {
        return map.getOrDefault(key, Collections.emptyList()).size();
    }
    
    public boolean isEmpty() {
        return map.isEmpty();
    }
    
    // Для итерации по всем парам
    public void forEach(BiConsumer<K, V> consumer) {
        map.forEach((key, values) -> values.forEach(value -> consumer.accept(key, value)));
    }
}

// Использование
CustomMultiMap<String, String> map = new CustomMultiMap<>();
map.put("tags", "java");
map.put("tags", "spring");
map.putAll("skills", Arrays.asList("coding", "design", "testing"));

map.forEach((key, value) -> 
    System.out.println(key + ": " + value)
);

Сравнение подходов

ПодходДубликатыПорядокПростотаЗависимости
Map<K, List<V>>сохраняет⭐⭐⭐нет
Map<K, Set<V>>не гарантирует⭐⭐⭐нет
Map<K, TreeSet<V>>отсортирован⭐⭐⭐нет
Apache MultiValueMapсохраняет⭐⭐commons-collections
Guava Multimapсохраняет⭐⭐guava
Stream groupingByсохраняет⭐⭐встроено

Рекомендации

Выбирайте в зависимости от ситуации:

  • Нет зависимостей?Map<K, List<V>> с computeIfAbsent
  • Дубликаты нежелательны?Map<K, Set<V>>
  • Нужна сортировка?Map<K, TreeSet<V>>
  • Работаете с Guava?Multimap
  • Apache Commons?MultiValuedMap
  • Трансформация коллекции?Stream.groupingBy

Мой личный выбор для production кода:

// Простое хранилище
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

// Если надо в Guava проекте
Multimap<String, String> map = ArrayListMultimap.create();

// Если трансформируем коллекцию
Map<String, List<Item>> grouped = items.stream()
    .collect(Collectors.groupingBy(Item::getCategory));