使用单个 Stream 将多个属性收集到 Map
Collecting multiple properties to a Map using a single Stream
我有一个包含多个属性的用户定义对象。由于它们都具有与 String
相同的数据类型,我想使用 Streams 将所有这些属性值收集到 Map
中。我不知道 Collectors.toMap()
或 Collectors.groupingBy()
可以提供此服务。
public class TeamMates{
private String playerFirstName;
private String playerLastName;
private String playerMaidenName;
//Constructors , getters and Setters
}
我有一个 List<TeamMates>
对象,我想用它来流式传输并将其收集到 Map<String,List<String>>
。
我希望地图包含
(playerFirstName,("John","Bob"));
(playerLastName,("Joe","Henry"));
(playerMaidenName,("H","K"));
因为我不想多次使用流式处理来单独收集每个 属性 或使用 forEach
,有没有办法通过单个流获得上述属性。
假设:
TeamMates
的一个实例保存了一个人的信息(应该叫TeamMate
吗?)
- 输出 Map 的键是文字字符串
"playerFirstName"
、"playerLastName"
、"playerMaidenName"
(而不是它们在问题中作为变量的显示方式)
- 目的是收集例如输入
List
中对象的所有名字到 List
对应于输出 Map
中的 "playerFirstName"
键
然后:
从初始 List
流式传输到结果 Map
的主要复杂性在于 Map
条目与 List
元素不对应。
这是一种方法,使用 Stream.collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) 多种收集方法。请参阅下面示例中的 main
方法。由于参数都是函数,我将它们提取到变量中以使流管道更易于阅读,并显示每个函数的类型:
public class TeamMate {
private static final String PLAYER_MAIDEN_NAME = "playerMaidenName";
private static final String PLAYER_LAST_NAME = "playerLastName";
private static final String PLAYER_FIRST_NAME = "playerFirstName";
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final Supplier<Map<String, List<String>>> supplier = () -> Map.of(
PLAYER_FIRST_NAME, new ArrayList<String>(),
PLAYER_LAST_NAME, new ArrayList<String>(),
PLAYER_MAIDEN_NAME, new ArrayList<String>());
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final BiConsumer<Map<String, List<String>>, TeamMate> accumulator = (r, tm) -> {
r.get(PLAYER_FIRST_NAME).add(tm.getPlayerFirstName());
r.get(PLAYER_LAST_NAME).add(tm.getPlayerLastName());
r.get(PLAYER_MAIDEN_NAME).add(tm.getPlayerMaidenName());
};
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final BiConsumer<Map<String, List<String>>, Map<String, List<String>>> combiner = (x, y) -> {
x.get(PLAYER_FIRST_NAME).addAll(y.get(PLAYER_FIRST_NAME));
x.get(PLAYER_LAST_NAME).addAll(y.get(PLAYER_LAST_NAME));
x.get(PLAYER_MAIDEN_NAME).addAll(y.get(PLAYER_MAIDEN_NAME));
};
private String playerFirstName;
private String playerLastName;
private String playerMaidenName;
TeamMate(String playerFirstName, String playerLastName, String playerMaidenName) {
this.playerFirstName = playerFirstName;
this.playerLastName = playerLastName;
this.playerMaidenName = playerMaidenName;
}
// getters & setters...
public static void main(String[] args) {
// input list
List<TeamMate> teamMates = List.of(new TeamMate("John", "Joe", "H"), new TeamMate("Bob", "Henry", "K"));
// single stream, as desired
Map<String, List<String>> output = teamMates.stream().collect(supplier, accumulator, combiner);
// output map
System.out.println(output);
// demonstration that results are as expected
assert output.containsKey(PLAYER_FIRST_NAME);
assert output.containsKey(PLAYER_LAST_NAME);
assert output.containsKey(PLAYER_MAIDEN_NAME);
assert output.get(PLAYER_FIRST_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerFirstName).toList());
assert output.get(PLAYER_LAST_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerLastName).toList());
assert output.get(PLAYER_MAIDEN_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerMaidenName).toList());
}
}
首先创建值生成 getter 方法及其各自字段名称的映射。
// Map.of is available from Java 9.
// If you are using java 8 you could create the below using a normal HashMap.
Map<String, Function<TeamMates, String>> mapper
= Map.of("playerFirstName", TeamMates::getPlayerFirstName,
"playerLastName", TeamMates::getPlayerLastName,
"playerMaidenName", TeamMates::getPlayerMaidenName);
现在迭代此映射器函数并将每个映射应用于队友列表
// applies getter method on each teammate object and returns the list of strings.
Function<Function<TeamMates, String>, List<String>> listMapper
= fn -> list.stream().map(fn::apply).collect(Collectors.toList());
// assume static import of Collectors.toMap
Map<String, List<String>> map = mapper.entrySet().stream()
.collect(toMap(Entry::getKey,
e -> listMapper.apply(e.getValue())));
我还建议将您的 class 命名为 TeamMate
而不是 TeamMates
,因为 class 代表单个队友实体。
我有一个包含多个属性的用户定义对象。由于它们都具有与 String
相同的数据类型,我想使用 Streams 将所有这些属性值收集到 Map
中。我不知道 Collectors.toMap()
或 Collectors.groupingBy()
可以提供此服务。
public class TeamMates{
private String playerFirstName;
private String playerLastName;
private String playerMaidenName;
//Constructors , getters and Setters
}
我有一个 List<TeamMates>
对象,我想用它来流式传输并将其收集到 Map<String,List<String>>
。
我希望地图包含
(playerFirstName,("John","Bob"));
(playerLastName,("Joe","Henry"));
(playerMaidenName,("H","K"));
因为我不想多次使用流式处理来单独收集每个 属性 或使用 forEach
,有没有办法通过单个流获得上述属性。
假设:
TeamMates
的一个实例保存了一个人的信息(应该叫TeamMate
吗?)- 输出 Map 的键是文字字符串
"playerFirstName"
、"playerLastName"
、"playerMaidenName"
(而不是它们在问题中作为变量的显示方式) - 目的是收集例如输入
List
中对象的所有名字到List
对应于输出Map
中的
"playerFirstName"
键
然后:
从初始 List
流式传输到结果 Map
的主要复杂性在于 Map
条目与 List
元素不对应。
这是一种方法,使用 Stream.collect(Supplier supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) 多种收集方法。请参阅下面示例中的 main
方法。由于参数都是函数,我将它们提取到变量中以使流管道更易于阅读,并显示每个函数的类型:
public class TeamMate {
private static final String PLAYER_MAIDEN_NAME = "playerMaidenName";
private static final String PLAYER_LAST_NAME = "playerLastName";
private static final String PLAYER_FIRST_NAME = "playerFirstName";
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final Supplier<Map<String, List<String>>> supplier = () -> Map.of(
PLAYER_FIRST_NAME, new ArrayList<String>(),
PLAYER_LAST_NAME, new ArrayList<String>(),
PLAYER_MAIDEN_NAME, new ArrayList<String>());
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final BiConsumer<Map<String, List<String>>, TeamMate> accumulator = (r, tm) -> {
r.get(PLAYER_FIRST_NAME).add(tm.getPlayerFirstName());
r.get(PLAYER_LAST_NAME).add(tm.getPlayerLastName());
r.get(PLAYER_MAIDEN_NAME).add(tm.getPlayerMaidenName());
};
/**
* Passed to Stream::collect, extracted out to show type
*/
private static final BiConsumer<Map<String, List<String>>, Map<String, List<String>>> combiner = (x, y) -> {
x.get(PLAYER_FIRST_NAME).addAll(y.get(PLAYER_FIRST_NAME));
x.get(PLAYER_LAST_NAME).addAll(y.get(PLAYER_LAST_NAME));
x.get(PLAYER_MAIDEN_NAME).addAll(y.get(PLAYER_MAIDEN_NAME));
};
private String playerFirstName;
private String playerLastName;
private String playerMaidenName;
TeamMate(String playerFirstName, String playerLastName, String playerMaidenName) {
this.playerFirstName = playerFirstName;
this.playerLastName = playerLastName;
this.playerMaidenName = playerMaidenName;
}
// getters & setters...
public static void main(String[] args) {
// input list
List<TeamMate> teamMates = List.of(new TeamMate("John", "Joe", "H"), new TeamMate("Bob", "Henry", "K"));
// single stream, as desired
Map<String, List<String>> output = teamMates.stream().collect(supplier, accumulator, combiner);
// output map
System.out.println(output);
// demonstration that results are as expected
assert output.containsKey(PLAYER_FIRST_NAME);
assert output.containsKey(PLAYER_LAST_NAME);
assert output.containsKey(PLAYER_MAIDEN_NAME);
assert output.get(PLAYER_FIRST_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerFirstName).toList());
assert output.get(PLAYER_LAST_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerLastName).toList());
assert output.get(PLAYER_MAIDEN_NAME).containsAll(teamMates.stream().map(TeamMate::getPlayerMaidenName).toList());
}
}
首先创建值生成 getter 方法及其各自字段名称的映射。
// Map.of is available from Java 9.
// If you are using java 8 you could create the below using a normal HashMap.
Map<String, Function<TeamMates, String>> mapper
= Map.of("playerFirstName", TeamMates::getPlayerFirstName,
"playerLastName", TeamMates::getPlayerLastName,
"playerMaidenName", TeamMates::getPlayerMaidenName);
现在迭代此映射器函数并将每个映射应用于队友列表
// applies getter method on each teammate object and returns the list of strings.
Function<Function<TeamMates, String>, List<String>> listMapper
= fn -> list.stream().map(fn::apply).collect(Collectors.toList());
// assume static import of Collectors.toMap
Map<String, List<String>> map = mapper.entrySet().stream()
.collect(toMap(Entry::getKey,
e -> listMapper.apply(e.getValue())));
我还建议将您的 class 命名为 TeamMate
而不是 TeamMates
,因为 class 代表单个队友实体。