How to create a key combination of Map from a list in Java Streams

Viewed 1305

I have a list of User object which I want to convert to a Map with key as FirstName+ Last Name and value as its ID. Is there any way to accomplish this using Streams ? . I tried the below but it didnt work since "Target type of the expression is not a functional interface". Should I use standard for loop to achieve this ? Any help is much appreciated.

List<User> users = userRepo.findAll();
Map<String,Long> userIdMap = users.stream()
                                  .collect(Collectors.toMap(User::getFirstName + "|" + User::getLastName, User::getId));
4 Answers

Type::method is a method reference. In the case of User::getFirstName, this is already of the type User -> String.

Functions in Java are not first class citizens. There are no operators for composing them or concatenating them. The + operator for example is not defined for User -> String (+) User -> String. It is defined for String + String, though.

So what you want is not to write bare method references, but to create an expression that is overall of the type User -> String, but utilizes the + operator for actual strings.

A lambda accepting a User needs that as first parameter, and it needs to return a string. u -> "Hello, World" would be such a lambda. Putting it together as needed, you end up with:

Function<User, String> userToFullName = u -> u.getFirstName() + "|" + u.getLastName();

You can then use that function inside your collector:

Function<User, String> userToFullName = user -> user.getFirstName() + "|"  + user.getLastName();
Map<String,Long> userIdMap = 
    users.stream()
    .collect(Collectors.toMap(userToFullName, User::getId));

Or, if you do not want to store the lambda explicitly in a named variable:

Map<String,Long> userIdMap = 
    users.stream()
    .collect(
        Collectors.toMap(
            user -> user.getFirstName() + "|"  + user.getLastName(), 
            User::getId
    ));
User::getFirstName + "|" + User::getLastName

Here, concat the method reference is the wrong way to concat strings. You can use lambda expression this way

Map<String,Long> userIdMap = 
       users.stream()
            .collect(Collectors.toMap(u -> u.getFirstName() + "|" + u.getLastName(),
                                      User::getId));

Polygnome gave an excellent answer to this. But I look at it slightly differently.

Consider the following:

User u = new User();
Function<User, String> fnc1 = User::getFirstName;

Neither of these are allowed because they aren't Strings, they're method references. But you were trying to use the first one as a String and the following won't compile.

String name = User::getFirstName;

To get the name you would need to do the following where u is the reference to the desired class. And if all you had was a single method reference, this is how it would have been resolved.

name = fnc1.apply(u);

But that didn't lend itself to your string construct of string concatenation so you really needed to do u.getFirstName() which the lambda u->u.getFirstName() permitted.

Just invoke the method with a lambda instead of a method reference.

List<User> users = userRepo.findAll();
       Map<String,Long> userIdMap = 
                  users.stream()
                  .collect(Collectors.toMap(u->u.getFirstName() + "|" + u.getLastName(), 
                  User::getId));

I think its best if you do not append the firstName and LastName, instead use a different approach like -

Map<Pair<String, String>,Integer> employeePairs = employees.stream().collect(HashMap<Pair<String, String>, Integer>::new,
            (m, c) -> m.put(Pair.of(c.getFirstName(), c.getLastName()), c.employeeId),
            (m, u) -> {});
    employeePairs.get(Pair.of("firstname", "lastname")); // Access the map in this way

This saves a lot of String Concatenation in case of Large Data.

For this you need to add a maven Package that gives you the Pair functionality

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.11</version>
</dependency>

And the import statement will be-

import org.apache.commons.lang3.tuple.Pair;
Related