2010-06-10 86 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); 
}