9

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 ?

3
  • just you a loop Commented May 15, 2018 at 10:45
  • 1
    Why specific Stream ? Commented May 15, 2018 at 10:46
  • 1
    @YCF_L He says juste next : I have List<User> so not use to understand all Commented May 15, 2018 at 10:47

4 Answers 4

15

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())));
Sign up to request clarification or add additional context in comments.

9 Comments

Miss one ) before collect
@RinsenS Please note I added a more efficient (and more elegant) solution.
@Eran, thanks a lot for it too !! but can i know whether how to acheive result map of type Map<Object, Object> instead of Map<String, List<String>>, where first Object is key (names) and second Object values is List<names>
@RinsenS Why would you want to do that? It's better to use the actual types than to use Map<Object, Object>.
@RinsenS If you insist, you can write Map<Object,Object> map = new HashMap<>(userList.stream()....);
|
6

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

4 Comments

thanks for the answer, but here we are streaming the userList 3 times right ??
@RinsenS Yes, we are. Be mindful to not optimise prematurely. Unless you have tens or hundreds of millions of users, it's not likely to be a performance bottleneck.
Yes its nice and clean too and thanks a lot for it !!
Even with millions of users, I wouldn’t be surprised if this solution, streaming three times is more efficient than the alternative. Streaming over a list is not an expensive operation per se. At least not as expensive as performing three million hash operations, compared to the three hash operations of this answer.
2

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.

2 Comments

I thought about a reflection solution, but the getter is called "name" and the map key is "names"
@Michael I think it is just a mistake from the op it still helpful for others i thought :)
1

Try this one. Color bean used to separating map value and key from the list.

    List<Color> colors = new ArrayList<Color>();

    colors.add(new Color("RED", "#FF0000"));
    colors.add(new Color("BLUE", "#0000FF"));
    colors.add(new Color("GREEN", "#008000"));

    // construct key-value pairs from name and code fields of Color
    Map<String, String> map = colors.stream()
                                .collect(Collectors.toMap(Color::getName, 
                                                        Color::getCode));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.