Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 List of Objects to Map<String, List> of values

Tags:

java

java-8

I am trying to convert List<Object> to Map<String, List> using Streams,

public class User{
   String name;
   String age;
   String org;
}

I have List<Users>, and need to collect into Map<String, Object> m,

 m.put("names", List of names,);
 m.put("age", List of age);
 m.put("org", List of org);

to be use in named query -> eg: select * from table ... where names in (:names) and age in (:age) and org in (:org)

as of now I am doing like

List<String> names = userList.stream().map(User::getName).collect(Collectors.toList());
List<String> age= userList.stream().map(User::getAge).collect(Collectors.toList());
List<String> org= userList.stream().map(User::getName).collect(Collectors.toList());

How to collect all the values while streaming to the list only once ?

like image 647
Rinsen S Avatar asked May 15 '18 10:05

Rinsen S


3 Answers

I believe something like this should work:

Map<String,List<String>> map =
    userList.stream()
            .flatMap(user -> {
                Map<String,String> um = new HashMap<>();
                um.put("names",user.getName());
                um.put("age",user.getAge());
                um.put("org",user.getOrg());
                return um.entrySet().stream();
            }) // produces a Stream<Map.Entry<String,String>>
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                                           Collectors.mapping(Map.Entry::getValue,
                                                              Collectors.toList())));

It converts each User to a Map<String,String> (containing the 3 required properties indexed by the required keys), and then groups the entries of all the user maps by their keys.

EDIT:

Here's another alternative that creates the Map.Entrys directly instead of creating the small HashMaps, so it should be more efficient:

Map<String,List<String>> map =
    userList.stream()
            .flatMap (user -> Stream.of (new SimpleEntry<>("names",user.getName()),
                                         new SimpleEntry<>("age",user.getAge()),
                                         new SimpleEntry<>("org",user.getOrg())))
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                                           Collectors.mapping(Map.Entry::getValue,
                                                              Collectors.toList())));
like image 197
Eran Avatar answered Nov 11 '22 22:11

Eran


Eran's showed you how you can accomplish this with streams. As you can hopefully see, it's incredibly ugly.

If your issue with your procedural version is the amount of code duplication, there are other ways besides streams that we can use to solve that problem.

I would refactor the collection to its own method:

private static List<String> getProperty(List<User> users, Function<User, String> getter) {
    return users.stream().map(getter).collect(Collectors.toList());
}

Map<String,List<String>> map = new HashMap<>();
map.put("names", getProperty(userList, User::getName));
map.put("age",   getProperty(userList, User::getAge));
map.put("org",   getProperty(userList, User::getOrg));
like image 22
Michael Avatar answered Nov 11 '22 20:11

Michael


Generic Solution

Both @Eran and @Michael gives a nice solution, I would like to solve your problem with a generic way :

public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
    List<User> listUsers = ...
    //Create a List which hold name of field and its value
    List<Map<String, Object>> listMapping = new ArrayList<>();
    for (User user : listUsers) {
        listMapping.add(fieldNameValue(user));
    }

    //Here group by the name of the field
    Map<String, List<Object>> result = listMapping.stream()
            .flatMap(a -> a.entrySet().stream())
            .collect(Collectors.groupingBy(Map.Entry::getKey,
                            Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

}

//This method return a Map which hold names of attributes and its values.
static Map<String, Object> fieldNameValue(Object obj) throws IllegalArgumentException, IllegalAccessException {
    Map<String, Object> mapping = new HashMap<>();
    for (Field field : obj.getClass().getDeclaredFields()) {
        field.setAccessible(true);
        mapping.put(field.getName(), field.get(obj));
    }
    return mapping;
}

In this solution you don't care about the number of fields of the type.

like image 37
YCF_L Avatar answered Nov 11 '22 20:11

YCF_L