英文:
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<User> users = userRepo.findAll();
Map<String,Long> userIdMap = users.stream()
.collect(Collectors.toMap(User::getFirstName + "|" + User::getLastName, User::getId));
答案1
得分: 7
Type::method
是一个方法引用。在 User::getFirstName
的情况下,这已经是类型为 User -> String
。
Java 中的函数不是一级公民。没有用于组合或连接它们的操作符。例如,+
运算符未定义为 User -> String (+) User -> String
。但对于 String + String
,它是被定义的。
所以你想要的不是直接编写裸露的方法引用,而是创建一个整体类型为 User -> String
的表达式,但利用 +
运算符来操作实际的字符串。
一个接受 User
的 lambda 需要将其作为第一个参数,并且它需要返回一个字符串。u -> "Hello, World"
就是这样一个 lambda。将它按需组合在一起,你得到了:
Function<User, String> userToFullName = u -> u.getFirstName() + "|" + u.getLastName();
然后你可以在收集器中使用该函数:
Function<User, String> userToFullName = user -> user.getFirstName() + "|" + user.getLastName();
Map<String,Long> userIdMap =
users.stream()
.collect(Collectors.toMap(userToFullName, User::getId));
或者,如果你不想将 lambda 显式地存储在一个命名变量中:
Map<String,Long> userIdMap =
users.stream()
.collect(
Collectors.toMap(
user -> user.getFirstName() + "|" + user.getLastName(),
User::getId
));
英文:
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
));
答案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 + "|" + 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));
答案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<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));
</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<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;
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论