[英]Create a Map<String, Set<String>> from a List<MyObject> using Stream API
I have a list of LoggerMessageDto
objects.我有一个
LoggerMessageDto
对象列表。
LoggerMessageDto
has two String
fields: message
and type
. LoggerMessageDto
有两个String
字段: message
和type
。
I want to convert this List
into a Map
with the following contents:我想将此
List
转换为具有以下内容的Map
:
key: "types", value: Set.of(LoggerMessageDto::gettype)
key: "messages" , value: Set.of(LoggerMessageDto::getMessage)
My attempt:我的尝试:
List<LoggerMessageDto> result = getSomeResult();
Set<String> journalTypes = new HashSet<>();
Set<String> messages = new HashSet<>();
result.forEach(item -> {
journalTypes.add(item.getType());
messages.add(item.getMessage());
});
String typesKey = "types";
String messagesKey = "messages";
Map<String, Set<String>> map = Map.of(
typesKey, journalTypes,
messagesKey, messages
);
How can I achieve this using Stream API?如何使用 Stream API 实现这一目标?
You can use Java 12 collector teeing
, which expects three arguments : two collectors and a function .您可以使用 Java 12 个收集器
teeing
,它需要三个 arguments :两个收集器和一个function 。 Each stream element gets consumed by both of the provided collectors and when they are done the function merges their results.每个 stream 元素都会被两个提供的收集器消耗,当它们完成时, function 会合并它们的结果。
As both downstream collectors of teeing
we can use collector mapping()
in conjunction with the collector toSet()
.作为
teeing
的两个下游收集器,我们可以将收集器mapping()
与收集器toSet()
结合使用。
public static Map<String, Set<String>> toMessagesByType(List<LoggerMessageDto> loggerMessageList,
String typesKey,
String messagesKey) {
return loggerMessageList.stream()
.collect(Collectors.teeing(
Collectors.mapping(LoggerMessageDto::getType, Collectors.toSet()),
Collectors.mapping(LoggerMessageDto::getMessage, Collectors.toSet()),
(types, messages) -> Map.of(
typesKey, types,
messagesKey, messages
)
));
}
Also, it's worth to mention that enums are more handy and reliable than strings, as @Joop Eggen has point out in the comment .此外,值得一提的是,枚举比字符串更方便和可靠,正如@Joop Eggen在评论中指出的那样。 Apart from saving you from typo which might occur while using strings, enums have an extensive language support (specialized collections:
EnumMap
, EnumSet
; they can be used in custom annotations; and in switch
expression/statements, etc.).除了避免使用字符串时可能出现的拼写错误之外,枚举还具有广泛的语言支持(专门的 collections:
EnumMap
、 EnumSet
;它们可用于自定义注释;以及switch
表达式/语句等)。
The Answer by Ivanchenko is correct and quite clever.伊万琴科的回答是正确的,非常聪明。 But that solution requires Java 12+, and you later commented that you must use Java 11.
但是该解决方案需要 Java 12+, 您后来评论说您必须使用 Java 11。
list.stream().map( getter ).collect( …
Here is another solution, this one working in Java 11.这是另一种解决方案,这个解决方案在 Java 11 中工作。
Here we run through the list twice.在这里,我们遍历列表两次。 On the first run, we use the getter method
type
to extract the type
member field on the Log
class.在第一次运行时,我们使用 getter 方法
type
来提取Log
class 上的type
成员字段。 We collect those strings into a Set
.我们将这些字符串收集到一个
Set
中。 Then we make a second run through the List
of Log
objects, this time extracting the message
member field by calling the getter message
.然后我们对
Log
对象List
进行第二次运行,这次通过调用 getter message
来提取message
成员字段。 Those strings are also collected into a Set
.这些字符串也被收集到一个
Set
中。
We put each of the two sets into a Map
.我们将两组中的每一个放入
Map
中。
Map < String, Set < String > > map =
Map.of(
"types" , list.stream().map( Log :: type ).collect( Collectors.toSet() ) ,
"messages" , list.stream().map( Log :: message ).collect( Collectors.toSet() )
);
Caveat: This approach is a little bit inefficient in that it loops the list twice, once for "types" and once for "messages".警告:这种方法有点低效,因为它循环列表两次,一次用于“类型”,一次用于“消息”。 But unless you are doing this many times for huge amounts of data, the impact on performance should be insignificant.
但除非您针对大量数据多次执行此操作,否则对性能的影响应该是微不足道的。
Generally best to work with unmodifiable collections until you know otherwise.通常最好与不可修改的 collections 一起使用,除非您另有了解。 So let's make those sets unmodifiable .
所以让我们让这些集合不可修改。 The
Map.of
is already producing an unmodifiable Map
object. Map.of
已经在生产不可修改的Map
object。
Map < String, Set < String > > map =
Map.of(
"types" , list.stream().map( Log :: type ).collect( Collectors.toUnmodifiableSet() ) ,
"messages" , list.stream().map( Log :: message ).collect( Collectors.toUnmodifiableSet() )
);
Here is a complete example.这是一个完整的例子。 For brevity I used the records feature in Java 16+, but you could just as well define a conventional class.
为简洁起见,我使用了 Java 16+ 中的记录功能,但您也可以定义一个常规的 class。 By the way, note that in Java 16+, records, enums, and interfaces can all be declared locally.
顺便提一下,在 Java 16+ 中,记录、枚举和接口都可以在本地声明。
record Log( String type , String message ) { }
List < Log > list =
List.of(
new Log( "INFO" , "dog" ) ,
new Log( "ERROR" , "dog" ) ,
new Log( "INFO" , "cat" ) ,
new Log( "INFO" , "cat" ) ,
new Log( "DEBUG" , "cat" )
);
Map < String, Set < String > > map =
Map.of(
"types" , list.stream().map( Log :: type ).collect( Collectors.toUnmodifiableSet() ) ,
"messages" , list.stream().map( Log :: message ).collect( Collectors.toUnmodifiableSet() )
);
System.out.println( "map = " + map );
map = {types=[INFO, DEBUG, ERROR], messages=[cat, dog]}
map = {类型=[信息,调试,错误],消息=[猫,狗]}
By the way, be aware that Set
and Map
by definition may iterate in any arbitrary order .顺便说一句,请注意
Set
和Map
根据定义可以以任意顺序迭代。 The order may change even between two iterations done one after another.即使在一个接一个地完成的两次迭代之间,顺序也可能发生变化。 If order matters to you, use a
NavigableSet
implementation and a NavigableMap
implementation.如果顺序对您很重要,请使用
NavigableSet
实现和NavigableMap
实现。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.