Ярлык для добавления для Списка в HashMap

У меня часто есть потребность взять список объектов и сгруппировать их в Карту на основе значения, содержавшегося в объекте. Например, возьмите список Пользователей и группы Страной.

Мой код для этого обычно похож:

Map> usersByCountry = new HashMap>();
for(User user : listOfUsers) {
    if(usersByCountry.containsKey(user.getCountry())) {
        //Add to existing list
        usersByCountry.get(user.getCountry()).add(user);

    } else {
        //Create new list
        List users = new ArrayList(1);
        users.add(user);
        usersByCountry.put(user.getCountry(), users);
    }
}

Однако я не могу сдержать взгляды, что это является неловким, и у некоторого гуру есть лучший подход. Самым близким, который я вижу до сих пор, является MultiMap от Google Collections.

Есть ли какие-либо стандартные подходы?

Спасибо!

50
задан Damo 10 June 2010 в 12:58
поделиться

6 ответов

В Java 8 вы можете использовать Map#computeIfAbsent().

Map<String, List<User>> usersByCountry = new HashMap<>();

for (User user : listOfUsers) {
    usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}

Или воспользуйтесь Collectors#groupingBy() Stream API, чтобы перейти от List к Map напрямую:

Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));

В Java 7 или ниже, лучшее, что вы можете получить:

Map<String, List<User>> usersByCountry = new HashMap<>();

for (User user : listOfUsers) {
    List<User> users = usersByCountry.get(user.getCountry());
    if (users == null) {
        users = new ArrayList<>();
        usersByCountry.put(user.getCountry(), users);
    }
    users.add(user);
}

Commons Collections имеет LazyMap, но он не параметризован. Guava не имеет LazyMap или LazyList, но вы можете использовать Multimap для этого, как показано в ответе polygenelubricants ниже.

69
ответ дан 7 November 2019 в 10:53
поделиться

Guava's Multimap действительно является наиболее подходящей структурой данных для этого, и на самом деле существует Multimaps.index (Iterable , Function ) , который делает именно то, что вы хотите: возьмите Iterable (которым является List ) и примените ] Функция , чтобы получить ключи для Multimap .

Вот пример из документации:

Например,

  Список  badGuys
 = Arrays.asList («Инки», «Блинки», «Пинки», «Пинки», «Клайд»);
Функция  stringLengthFunction = ...;
Multimap  index
 = Multimaps.index (badGuys, stringLengthFunction);
System.out.println (индекс);

печатает

  {4 = [Inky], 5 = [Pinky, Pinky, Clyde], 6 = [Blinky]}.

В вашем случае вы должны написать Function userCountryFunction = ... .

20
ответ дан 7 November 2019 в 10:53
поделиться

Используя lambdaj , вы можете получить этот результат с помощью всего одной строчки кода, как показано ниже:

Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));

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

2
ответ дан 7 November 2019 в 10:53
поделиться

Когда мне приходится иметь дело с коллекционно-значимой картой, я почти всегда пишу небольшой статический метод putIntoListMap() в классе. Если он нужен в нескольких классах, я переношу этот метод в класс-утилиту. Подобные вызовы статических методов немного уродливы, но они гораздо чище, чем каждый раз набирать код. Если только мультикарты не играют довольно важную роль в вашем приложении, IMHO, вероятно, не стоит добавлять еще одну зависимость.

2
ответ дан 7 November 2019 в 10:53
поделиться

Чистый и читабельный способ добавления элемента выглядит так:

String country = user.getCountry();
Set<User> users
if (users.containsKey(country))
{
    users = usersByCountry.get(user.getCountry());
}
else
{
    users = new HashSet<User>();
    usersByCountry.put(country, users);
}
users.add(user);

Обратите внимание, что вызов containsKey и get не медленнее, чем просто вызов get и проверка результата на null.

0
ответ дан 7 November 2019 в 10:53
поделиться

Похоже, что ваши потребности удовлетворяются с помощью LinkedHashMultimap в библиотеке GC. Если вы можете жить с зависимостями, весь ваш код будет выглядеть следующим образом:

SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);

порядок вставки сохраняется (почти все, что вы делали со своим списком), и дублирование исключено; вы, конечно, можете переключиться на простой набор на основе хешей или набор деревьев, как того требует необходимость (или список, хотя, похоже, это не то, что вам нужно). Пустые коллекции возвращаются, если вы запрашиваете страну без пользователей, все получают пони и т. Д. - я имею в виду, проверьте API. Это будет очень полезно для вас, так что зависимость может того стоить.

1
ответ дан 7 November 2019 в 10:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: