Подтвердить что ты не робот

Ярлык для добавления в список в 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.

Существуют ли какие-либо стандартные подходы?

Спасибо!

4b9b3361

Ответ 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);
}