用于在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 Collections的MultiMap 。

有没有标准的方法?

谢谢!

没有人想到目前为止。 我只是将其优化如下,包括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 ,但没有参数化。 番石榴没有LazyMapLazyListsorting,但是你可以使用Multimap ,如下面 LazyList 回答所示 。


更新如果你碰巧在Java 8上已经有了所有的lambda awesomeness,只要使用Map#computeIfAbsent() 。 上面的例子可以简化如下:

 Map<String, List<User>> usersByCountry = new HashMap<>(); for (User user : listOfUsers) { usersByCountry.computeIfAbsent(user.getCountry(), v -> new ArrayList<>()).add(user); } 

或者,利用streamAPI直接从ListMap

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

番石榴的Multimap确实是最合适的数据结构,事实上, Multimaps.index(Iterable<V>, Function<? super V,K>)实用程序方法完全符合你的要求:取一个可Iterable<V>List<V>是),并应用Function<? super V, K> 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 = ...

当我不得不处理一个集合值映射的时候,我总是在类中写一点putIntoListMap()静态工具方法。 如果我发现自己需要在多个类中,我把这个方法放到一个实用类中。 像这样的静态方法调用有点难看,但比每次input代码都要干净得多。 除非多图在你的应用程序中扮演一个非常重要的angular色,恕我直言,这可能是不值得的拉入另一个依赖。

通过使用lambdaj,你可以只用一行代码就可以得到结果:

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

Lambdaj还提供了许多其他function来操作具有非常可读的域特定语言的集合。

我们似乎做了很多次,所以我创build了一个模板类

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

看起来您的确切需求是由GC库中的LinkedHashMultimap满足的。 如果你可以忍受依赖关系,你所有的代码变成:

 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和testingnull的结果慢。

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