用于在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
,但没有参数化。 番石榴没有LazyMap
或LazyList
sorting,但是你可以使用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直接从List
到Map
:
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);
请注意,调用containsKey
和get
不会比调用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); }