У меня часто есть потребность взять список объектов и сгруппировать их в Карту на основе значения, содержавшегося в объекте. Например, возьмите список Пользователей и группы Страной.
Мой код для этого обычно похож:
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.
Есть ли какие-либо стандартные подходы?
Спасибо!
В 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 ниже.
Guava's Multimap
действительно является наиболее подходящей структурой данных для этого, и на самом деле существует Multimaps.index (Iterable
, который делает именно то, что вы хотите: возьмите Iterable
(которым является List
) и примените ] Функция super V, K>
, чтобы получить ключи для Multimap
.
Вот пример из документации:
Например,
Список
badGuys = Arrays.asList («Инки», «Блинки», «Пинки», «Пинки», «Клайд»); Функция stringLengthFunction = ...; Multimap index = Multimaps.index (badGuys, stringLengthFunction); System.out.println (индекс); печатает
{4 = [Inky], 5 = [Pinky, Pinky, Clyde], 6 = [Blinky]}.
В вашем случае вы должны написать Function
.
Используя lambdaj , вы можете получить этот результат с помощью всего одной строчки кода, как показано ниже:
Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));
Lambdaj также предлагает множество других функций для управления коллекциями с очень читаемый язык, специфичный для домена.
Когда мне приходится иметь дело с коллекционно-значимой картой, я почти всегда пишу небольшой статический метод putIntoListMap() в классе. Если он нужен в нескольких классах, я переношу этот метод в класс-утилиту. Подобные вызовы статических методов немного уродливы, но они гораздо чище, чем каждый раз набирать код. Если только мультикарты не играют довольно важную роль в вашем приложении, IMHO, вероятно, не стоит добавлять еще одну зависимость.
Чистый и читабельный способ добавления элемента выглядит так:
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
.
Похоже, что ваши потребности удовлетворяются с помощью LinkedHashMultimap в библиотеке GC. Если вы можете жить с зависимостями, весь ваш код будет выглядеть следующим образом:
SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);
порядок вставки сохраняется (почти все, что вы делали со своим списком), и дублирование исключено; вы, конечно, можете переключиться на простой набор на основе хешей или набор деревьев, как того требует необходимость (или список, хотя, похоже, это не то, что вам нужно). Пустые коллекции возвращаются, если вы запрашиваете страну без пользователей, все получают пони и т. Д. - я имею в виду, проверьте API. Это будет очень полезно для вас, так что зависимость может того стоить.