2010-06-10 87 views
37

我经常需要获取对象列表,并根据对象中包含的值将它们组合到一个Map中。例如。按国家列出用户和组。在HashMap中添加列表的快捷方式

我给这家代码通常是这样的:

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 from Google Collections

是否有任何标准方法?

谢谢!

+1

应该就是真的是'Map >'?答案对您选择构建或使用的内容有所影响。请注意,Google Collections为各种类型的列表和集合提供了嵌套集合的细化。 – seh 2010-06-11 00:04:12

+0

只需放弃.Net和Linq的Java。 – 2010-06-11 00:11:10

+1

@Hamish:是的,因为我们担心依赖关系是完全不相关的! – Carl 2010-06-11 01:20:22

回答

49

在Java 8中,您可以使用Map#computeIfAbsent()

Map<String, List<User>> usersByCountry = new HashMap<>(); 

for (User user : listOfUsers) { 
    usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user); 
} 

或者,利用流API的Collectors#groupingBy()去从ListMap直接:

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 CollectionsLazyMap,但它没有参数化。 Guava没有排序LazyMapLazyList,但是您可以使用Multimap进行此操作,如answer of polygenelubricants below中所示。

+0

你可以缩短一点:'usersByCountry.put(user.getCountry(),users = new ArrayList <>());'虽然我确信有人会对此皱眉。 – shmosel 2017-03-17 01:30:23

+0

我知道这并不重要,但也许新手需要知道映射函数将获得作为参数的关键,所以最好使用'k'而不是'v'usersByCountry.computeIfAbsent(user.getCountry( ),k - > new ArrayList <>())。add(user);' – 2017-11-28 11:29:34

2

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

+0

另外,BalusC的优化是一个很好的知识。 – 2010-06-11 00:12:36

1

看起来您的确切需求在GC库中被LinkedHashMultimap所满足。如果你能依赖生活,所有的代码变成:

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

插入顺序维护(所有它看起来像你用你的清单做)和重复被排除;你当然可以切换到一个简单的基于散列的集合或根据需要指定的树集(或列表,尽管这似乎不是你需要的)。如果您要求一个没有用户的国家,每个人都会得到小马等,那么空集合会被返回 - 我的意思是,请查看API。它会为你做很多事情,所以依赖可能是值得的。

+0

+1谢谢,这是很好的知道,但BalusC的optinmization是我所追求的。 – Damo 2010-06-11 06:16:35

0

干净和可读的方式添加元素如下:

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慢。

+0

本身的调用确实不慢,但查找现在会发生两次而不是一次。 – BalusC 2010-06-11 11:07:00

+0

我澄清了它。 – starblue 2010-06-11 18:47:19

19

番石榴的Multimap确实是这样做的最合适的数据结构,而事实上,有Multimaps.index(Iterable<V>, Function<? super V,K>)实用方法,它正是你想要的:采取Iterable<V>(其中List<V>是),并应用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 = ...

+2

+1令我感到沮丧的是,涉及编写比这个更多的代码的答案排名较高,仅仅因为它们是最快进来的。:( – 2010-06-11 16:37:54

+2

@Kevin:我希望你最终会停下来=)顺便说一句,我打算最终在各种番石榴类上编写关于stackoverflow的Q/A文章以展示其功能。 – polygenelubricants 2010-06-11 16:39:10

+2

我每天只停留一次或两次,从而保证我永远没有机会得到我的答案。 我认为你的想法很棒。我假设你是指张贴问题并自己回答。你会得到一些人告诉你这是不道德的,但是它被更广泛的SO社区明确认可,因为他们的目标是让SO有很好的内容。例如, – 2010-06-11 16:47:22

2

通过使用lambdaj可以获取只用一行代码,结果,因为它遵循:

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

Lambdaj还提供了许多其他功能操作的集合与一个非常可读的领域特定语言。

+0

+1这很好。看起来非常有用。 – Damo 2010-06-13 21:53:11

2

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

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

提供IMPL

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