Multi-map в Java 8


Мультикарта (multi-map) может пригодиться в реальной работе или на собеседовании. Почему-то в некоторых компаниях при приёме на работу любят давать алгоритмические задачки на её использование (лично я так не поступаю).
По сути это обычная карта (Map) в которой значением является коллекция (List или Set).
Сейчас в JCF (Java Collections Framework) нет готового класса для Multimap.

До выхода Java 8 приходилось логику работы писать вручную.
Например так.
Допустим, нам поступают такие данные:

Moscow=ru
Omsk=ru
Tula=ru
NY=us
LA=us
London=uk и т.д.

Их нужно представить в виде:

ru=Moscow, Omsk, Tula
us=NY, LA
uk=London

Тогда логику можно реализовать, например, в таком виде:

import java.util.*;
public class Main {
    public static void main(String[] args) {
        // Получили город и страну
        String city = "Moscow", country = "ru";
        // Карта куда всё будем складывать
        Map> map = new HashMap<>();

        List list = map.get(country);
        // Если это наш первый раз, 
        // тогда создаем список и кладем его по ключу (название страны) 
        if (list == null) {
            list = new ArrayList<>();
            map.put(country, list);
        }
        list.add(city);
    }
}

Это не удобно, поэтому многие используют Multimap из Apache Commons или Google Guava.

В восьмой Java, с помощью анонимных функций и нового API, логику можно сделать однострочником:

    // В случае первого раза вызывать функцию: (key) -> value
    map.computeIfAbsent(country, (k)-> new ArrayList()).add(city);

N.B.: Совет использовать computeIfAbsent для создания мультикарты явно указан в документации.

Если же у нас на входе готовая карта, то её можно прокрутить foreach (хотя это не очень красиво):
Например так:

 Map map = new HashMap<>();
 map.put("Madrid", "es");
 map.put("Moscow", "ru");
 map.put("Omsk", "ru");
 map.put("London", "uk");
 
 Map> result = new HashMap<>();
 map.forEach((city, country) -> {
      result.computeIfAbsent(country, (k) -> new ArrayList()).add(city); 
 });

Интересней было бы воспользоваться Stream API.
Например так:

import java.util.*;
import static java.util.Map.Entry;
import static java.util.stream.Collectors.*;

public class Main {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("Madrid", "es");
        map.put("Moscow", "ru");
        map.put("Omsk", "ru");
        map.put("London", "uk");
        // Сгруппировать по стране (Entry::getValue),
        // а название города Entry::getKey записывать в список (toList)             
        Map> m = map.entrySet().stream().collect(
                // Группировать по названию страны (es, ru, uk и т.д.)
                groupingBy(Entry::getValue,
                      // Запихиваем город (Moscow, London и т.д.) в список (toList)
                      mapping(Entry::getKey, toList())));
    }
}

Здесь используется функциональное программирование, восприятие которого требует определенного “привыкания”. Поскольку я периодически сталкиваюсь как со сторонниками “императивщины”, так и со сторонниками “функциональщины”, могу сказать, что у каждого своя правда. В каждом конкретном случае следует исходить из конечных целей.

В качестве причины почему действительно стоит изучать функциональный поход могу сказать, что игнорировать его сейчас уже нельзя, т.к. в противном случае программист просто перестанет понимать программы написанные другими программистами. Когда появилась 5-ая Java было очень много ненавистников дженериков, сейчас польза их очевидна, а умение их использовать просто необходимо.

Любое использование либо копирование материалов или подборки материалов сайта, элементов дизайна и оформления допускается лишь с разрешения правообладателя и только со ссылкой на источник: programador.ru

Телеграм канал: @prgrmdr
Почта для связи: vit [at] programmisty.com