如何在Java Streams中从列表创建Map的键组合

huangapple go评论62阅读模式
英文:

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

问题

我有一个 User 对象的列表,我想将其转换为一个 Map,以 FirstName+LastName 作为键,以其 ID 作为值。是否有办法使用 Streams 来实现这一点?我尝试了下面的代码,但由于“表达式的目标类型不是函数接口”,它没有起作用。我应该使用标准的 for 循环来实现这个吗?非常感谢帮助。

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

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&lt;User&gt; users = userRepo.findAll();
Map&lt;String,Long&gt; userIdMap = users.stream()
								  .collect(Collectors.toMap(User::getFirstName + &quot;|&quot; + User::getLastName, User::getId));

答案1

得分: 7

Type::method 是一个方法引用。在 User::getFirstName 的情况下,这已经是类型为 User -&gt; String

Java 中的函数不是一级公民。没有用于组合或连接它们的操作符。例如,+ 运算符未定义为 User -&gt; String (+) User -&gt; String。但对于 String + String,它是被定义的。

所以你想要的不是直接编写裸露的方法引用,而是创建一个整体类型为 User -&gt; String 的表达式,但利用 + 运算符来操作实际的字符串。

一个接受 User 的 lambda 需要将其作为第一个参数,并且它需要返回一个字符串。u -&gt; &quot;Hello, World&quot; 就是这样一个 lambda。将它按需组合在一起,你得到了:

Function&lt;User, String&gt; userToFullName = u -&gt; u.getFirstName() + &quot;|&quot; + u.getLastName();

然后你可以在收集器中使用该函数:

Function&lt;User, String&gt; userToFullName = user -&gt; user.getFirstName() + &quot;|&quot;  + user.getLastName();
Map&lt;String,Long&gt; userIdMap = 
    users.stream()
    .collect(Collectors.toMap(userToFullName, User::getId));

或者,如果你不想将 lambda 显式地存储在一个命名变量中:

Map&lt;String,Long&gt; userIdMap = 
    users.stream()
    .collect(
        Collectors.toMap(
            user -&gt; user.getFirstName() + &quot;|&quot;  + user.getLastName(), 
            User::getId
    ));
英文:

Type::method is a method reference. In the case of User::getFirstName, this is already of the type User -&gt; 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 -&gt; String (+) User -&gt; 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 -&gt; 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 -&gt; &quot;Hello, World&quot; would be such a lambda. Putting it together as needed, you end up with:

Function&lt;User, String&gt; userToFullName = u -&gt; u.getFirstName() + &quot;|&quot; + u.getLastName();

You can then use that function inside your collector:

Function&lt;User, String&gt; userToFullName = user -&gt; user.getFirstName() + &quot;|&quot;  + user.getLastName();
Map&lt;String,Long&gt; 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&lt;String,Long&gt; userIdMap = 
    users.stream()
    .collect(
        Collectors.toMap(
            user -&gt; user.getFirstName() + &quot;|&quot;  + user.getLastName(), 
            User::getId
    ));

答案2

得分: 2

User::getFirstName + "|" + User::getLastName

这里,使用方法引用来连接字符串是错误的方式。你可以使用 lambda 表达式来实现这样的连接:

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

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

Map&lt;String,Long&gt; userIdMap = 
       users.stream()
            .collect(Collectors.toMap(u -&gt; u.getFirstName() + &quot;|&quot; + u.getLastName(),
                                      User::getId));

答案3

得分: 2

Polygnome 对此给出了很好的答案。 但我有稍微不同的看法。

考虑以下内容:

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

这两者都不被允许,因为它们不是字符串,而是方法引用。但是您试图将第一个用作字符串,接下来的代码将无法编译。

String name = User::getFirstName;

要获取名称,您需要执行以下操作,其中 u 是对所需类的引用。如果您只有一个方法引用,就会这样解析。

name = fnc1.apply(u);

但是这不能满足您构建字符串连接的要求,所以您真正需要执行的操作是
u.getFirstName(),而 lambda u->u.getFirstName() 则是允许的。

只需使用 lambda 调用方法,而不是使用方法引用。

List<User> users = userRepo.findAll();
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&lt;User, String&gt; 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-&gt;u.getFirstName() permitted.

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

List&lt;User&gt; users = userRepo.findAll();
       Map&lt;String,Long&gt; userIdMap = 
                  users.stream()
                  .collect(Collectors.toMap(u-&gt;u.getFirstName() + &quot;|&quot; + u.getLastName(), 
                  User::getId));

</details>



# 答案4
**得分**: 0

我认为最好的做法是不要追加firstName和LastName,而是使用不同的方法,例如 -

    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")); // 通过这种方式访问映射

这在处理大量数据时可以节省很多字符串连接的操作。

为此,您需要添加一个提供Pair功能的Maven包

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

导入语句将是 -

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


<details>
<summary>英文:</summary>

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

    Map&lt;Pair&lt;String, String&gt;,Integer&gt; employeePairs = employees.stream().collect(HashMap&lt;Pair&lt;String, String&gt;, Integer&gt;::new,
                (m, c) -&gt; m.put(Pair.of(c.getFirstName(), c.getLastName()), c.employeeId),
                (m, u) -&gt; {});
        employeePairs.get(Pair.of(&quot;firstname&quot;, &quot;lastname&quot;)); // 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

    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
        &lt;artifactId&gt;commons-lang3&lt;/artifactId&gt;
        &lt;version&gt;3.11&lt;/version&gt;
    &lt;/dependency&gt;

 And the import statement will be-

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




</details>



huangapple
  • 本文由 发表于 2020年10月5日 21:31:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/64209636.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定