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. 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.
Are there any standard approaches?
Thanks!
There are two methods to add elements to the list. add(E e): appends the element at the end of the list. Since List supports Generics, the type of elements that can be added is determined when the list is created. add(int index, E element): inserts the element at the given index.
In Java 8 you can make use of 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:
Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));
In Java 7 or below, best what you can get is below:
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. 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'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 a List<V>
is), and apply the Function<? super V, K>
to get the keys for the 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 = ...
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With