Ярлык для добавления в список в HashMap
Мне часто приходится брать список объектов и группировать их в Map на основе значения, содержащегося в объекте. Например. возьмите список пользователей и группу по странам.
Мой код для этого обычно выглядит следующим образом:
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
if(usersByCountry.containsKey(user.getCountry())) {
//Add to existing list
usersByCountry.get(user.getCountry()).add(user);
} else {
//Create new list
List<User> users = new ArrayList<User>(1);
users.add(user);
usersByCountry.put(user.getCountry(), users);
}
}
Однако я не могу не думать о том, что это неудобно, и у какого-то гуру есть лучший подход. Самое близкое, что я вижу до сих пор, это MultiMap из Коллекций Google.
Существуют ли какие-либо стандартные подходы?
Спасибо!
Ответы
Ответ 1
В Java 8 вы можете использовать Map#computeIfAbsent()
.
Map<String, List<User>> usersByCountry = new HashMap<>();
for (User user : listOfUsers) {
usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}
Или используйте Stream API Collectors#groupingBy()
, чтобы перейти от 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 имеет LazyMap
, но он не параметризуется. Guava не имеет типа LazyMap
или LazyList
, но вы можете использовать Multimap
для этого, как показано в ответе полигенных смазочных материалов ниже.
Ответ 2
Guava Multimap
действительно является самой подходящей структурой данных для этого, и на самом деле существует Multimaps.index(Iterable<V>, Function<? super V,K>)
метод утилиты, который делает именно то, что вы хотите: возьмите Iterable<V>
(который List<V>
is) и примените Function<? super V, K>
для получения ключей для Multimap<K,V>
.
Вот пример из документации:
Например,
List<String> badGuys
= Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
Function<String, Integer> stringLengthFunction = ...;
Multimap<Integer, String> index
= Multimaps.index(badGuys, stringLengthFunction);
System.out.println(index);
печатает
{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
В вашем случае вы напишете Function<User,String> userCountryFunction = ...
.
Ответ 3
Когда мне приходится иметь дело с картографией, имеющей коллекцию, я почти всегда начинаю писать небольшой статический метод putIntoListMap() в классе. Если я нахожусь в нем в нескольких классах, я бросаю этот метод в класс утилиты. Статические вызовы вроде этого немного уродливы, но они намного чище, чем каждый раз вводить код. Если мульти-карты играют довольно важную роль в вашем приложении, IMHO, вероятно, не стоит тянуть другую зависимость.
Ответ 4
Используя lambdaj, вы можете получить этот результат только с одной строкой кода:
Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));
Lambdaj также предлагает множество других функций для управления коллекциями с очень удобным для чтения языком.
Ответ 5
Мы, кажется, делаем это много раз, поэтому я создал класс шаблонов
public abstract class ListGroupBy<K, T> {
public Map<K, List<T>> map(List<T> list) {
Map<K, List<T> > map = new HashMap<K, List<T> >();
for (T t : list) {
K key = groupBy(t);
List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>();
innerList.add(t);
map.put(key, innerList);
}
return map;
}
protected abstract K groupBy(T t);
}
Вы просто предоставляете impl для groupBy
в вашем случае
String groupBy(User u){return user.getCountry();}
Ответ 6
Похоже, ваши точные потребности удовлетворяются LinkedHashMultimap в библиотеке GC. Если вы можете жить с зависимостями, весь ваш код будет выглядеть следующим образом:
SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);
порядок вставки поддерживается (все это похоже на то, что вы делали с вашим списком), и дубликаты исключены; вы можете, конечно, переключиться на простой хэш-набор или набор деревьев по мере необходимости (или список, хотя это, похоже, не то, что вам нужно). Пустые коллекции возвращаются, если вы запрашиваете страну без пользователей, каждый получает пони и т.д. - я имею в виду, проверьте API. Это многое сделает для вас, поэтому зависимость может стоить того.
Ответ 7
Чистым и понятным способом добавления элемента является следующее:
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
.
Ответ 8
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
List<User> users = usersByCountry.get(user.getCountry());
if (users == null) {
usersByCountry.put(user.getCountry(), users = new ArrayList<User>());
}
users.add(user);
}