简体   繁体   English

在 HashMap 中添加到列表的快捷方式

[英]Shortcut for adding to List in a HashMap

I often have a need to take a list of objects and group them into a Map based on a value contained in the object.我经常需要获取一个对象列表,然后根据 object 中包含的值将它们分组到 Map 中。 Eg.例如。 take a list of Users and group by Country.按国家/地区列出用户和组。

My code for this usually looks like:我的代码通常如下所示:

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

However I can't help thinking that this is awkward and some guru has a better approach.但是我不禁认为这很尴尬,一些大师有更好的方法。 The closest I can see so far is the MultiMap from Google Collections .到目前为止,我能看到的最接近的是Google Collections 的 MultiMap

Are there any standard approaches?有没有标准的方法?

Thanks!谢谢!

Since Java 8 you can make use of Map#computeIfAbsent() .从 Java 8 开始,您可以使用Map#computeIfAbsent()

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

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

Or, make use of Stream API's Collectors#groupingBy() to go from List to Map directly:或者,利用 Stream API 的Collectors#groupingBy()直接从List转到Map

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

In Java 7 or below, best what you can get is below:在 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 has a LazyMap , but it's not parameterized. Commons Collections有一个LazyMap ,但它没有参数化。 Guava doesn't have sort of a LazyMap or LazyList , but you can use Multimap for this as shown in answer of polygenelubricants below . Guava没有某种LazyMapLazyList ,但您可以为此使用Multimap ,如下面的 polygenelubricants 答案所示

Guava's Multimap really is the most appropriate data structure for this, and in fact, there is Multimaps.index(Iterable<V>, Function<? super V,K>) utility method that does exactly what you want: take an Iterable<V> (which aList<V> is), and apply the Function<? super V, K> Guava 的Multimap确实是最合适的数据结构,事实上,有Multimaps.index(Iterable<V>, Function<? super V,K>)实用方法可以满足您的要求:采用Iterable<V>List<V>是),并应用Function<? super V, K> Function<? super V, K> to get the keys for the Multimap<K,V> . Function<? super V, K>获取Multimap<K,V>的键。

Here's an example from the documentation:这是文档中的一个示例:

For example,例如,

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

prints印刷

 {4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}

In your case you'd write a Function<User,String> userCountryFunction = ... .在您的情况下,您将编写一个Function<User,String> userCountryFunction = ...

We seem to do this a lot of times so I created a template class我们似乎经常这样做,所以我创建了一个模板类

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

You just provide impl for groupBy您只需为 groupBy 提供 impl

in your case在你的情况下

String groupBy(User u){return user.getCountry();}

When I have to deal with a collection-valued map, I just about always wind up writing a little putIntoListMap() static utility method in the class.当我必须处理一个集合值映射时,我几乎总是在类中编写一个小的 putIntoListMap() 静态实用程序方法。 If I find myself needing it in multiple classes, I throw that method into a utility class.如果我发现自己在多个类中都需要它,我会将该方法放入实用程序类中。 Static method calls like that are a bit ugly, but they're much cleaner than typing the code out every time.像这样的静态方法调用有点难看,但它们比每次都输入代码要干净得多。 Unless multi-maps play a pretty central role in your app, IMHO it's probably not worth it to pull in another dependency.除非多地图在您的应用程序中扮演非常重要的角色,否则恕我直言,引入另一个依赖项可能不值得。

By using lambdaj you can obtain that result with just one line of code as it follows:通过使用lambdaj ,您只需一行代码即可获得该结果,如下所示:

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

Lambdaj also offers lots of other features to manipulate collections with a very readable domain specific language. Lambdaj 还提供了许多其他功能来使用非常易读的领域特定语言来操作集合。

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

It looks like your exact needs are met by LinkedHashMultimap in the GC library. GC 库中的LinkedHashMultimap似乎满足了您的确切需求。 If you can live with the dependencies, all your code becomes:如果您可以忍受依赖项,那么您的所有代码都将变为:

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

insertion order is maintained (about all it looks like you were doing with your list) and duplicates are precluded;维护插入顺序(看起来就像您对列表所做的一切)并且排除了重复项; you can of course switch to a plain hash-based set or a tree set as needs dictate (or a list, though that doesn't seem to be what you need).您当然可以根据需要切换到基于散列的普通集或树集(或列表,尽管这似乎不是您所需要的)。 Empty collections are returned if you ask for a country with no users, everyone gets ponies, etc - what I mean is, check out the API.如果您要求一个没有用户的国家/地区,每个人都有小马等,则会返回空集合 - 我的意思是,请查看 API。 It'll do a lot for you, so the dependency might be worth it.它会为你做很多事情,所以依赖可能是值得的。

A clean and readable way to add an element is the following:添加元素的一种简洁易读的方法如下:

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

Note that calling containsKey and get is not slower than just calling get and testing the result for null .请注意,调用containsKeyget并不比仅调用get并测试null的结果慢。

ArrayList numbersList = new ArrayList<>(Arrays.asList(1, 1, 2, 3, 3, 3, 4, 5, 6, 6, 6, 7, 8)); ArrayList numbersList = new ArrayList<>(Arrays.asList(1, 1, 2, 3, 3, 3, 4, 5, 6, 6, 6, 7, 8));

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

System.out.println(elementCountMap); System.out.println(elementCountMap);

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM