在 HashMap 中添加 List 的快捷方式

我经常需要获取一个对象列表,并根据对象中包含的值将它们分组到一个 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);
}
}

然而,我不禁认为这是尴尬的,一些大师有一个更好的方法。目前为止我能看到的最接近的是 来自 Google 的 MultiMap

有什么标准方法吗?

谢谢!

136322 次浏览

由于 Java8,您可以使用 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));

在 Java7或以下版本,你所能得到的最佳资料如下:

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,但它没有参数化。番石榴没有 LazyMap或者 LazyList,但是你可以使用 Multimap,如 多基因润滑剂的答案如下所示。

当我必须处理集合值映射时,我几乎总是在类中编写一个小的 putIntoListMap ()静态实用程序方法。如果我发现自己在多个类中需要它,我会将该方法扔到一个实用程序类中。像这样的静态方法调用有点难看,但是它们比每次输入代码要干净得多。除非多地图在你的应用程序中扮演了一个相当重要的角色,恕我直言,它可能不值得拉进另一个依赖。

看起来你的确切需求在 GC 图书馆的 ABc0中得到了满足。如果您能够忍受这些依赖项,那么您的所有代码都将变成:

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

插入顺序得到了维护(看起来就像你对你的列表所做的一样) ,重复的内容被排除在外; 你当然可以根据需要切换到一个普通的基于散列的集合或者一个树集合(或者一个列表,尽管这似乎不是你所需要的)。如果您要求一个没有用户的国家,每个人都得到小马,等等-我的意思是,检查 API 的空集合返回。这对你有很大帮助,所以这种依赖也许是值得的。

添加元素的简洁易读的方法如下:

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);

请注意,调用 containsKeyget并不比仅仅调用 get和测试 null的结果慢。

番石榴的 Multimap确实是最合适的数据结构,事实上,有 Multimaps.index(Iterable<V>, Function<? super V,K>)实用方法可以完全满足您的需要: 取一个 Iterable<V>(List<V>就是 Iterable<V>) ,然后应用 Multimaps.index(Iterable<V>, Function<? super V,K>)0来获得 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 = ...

通过使用 (德语),只需一行代码就可以得到如下结果:

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

Lambdaj 还提供了许多其他特性,可以用一种非常易读的特定于领域的语言来操作集合。

我们似乎经常这样做,所以我创建了一个模板类

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);
}

你只需要为 groupBy 提供暗示

在你的情况下

String groupBy(User u){return user.getCountry();}
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);
}

数组列表编号列表 = new ArrayList < > (Arrays.asList (1,1,2,3,3,3,4,5,6,6,6,7,8)) ;

Map < Integer,Long > elementCountMap = numbersList.stream () . Collection (Collectors.toMap (Function.Identity () ,v-> 1L,Long: : sum)) ;

Println (elementCountMap) ;

O/p: {1 = 2,2 = 1,3 = 3,4 = 1,5 = 1,6 = 3,7 = 1,8 = 1}