Java 12 - Collectors.teeing
You can use Java 12 collector teeing, which expects three arguments: two collectors and a function. Each stream element gets consumed by both of the provided collectors and when they are done the function merges their results.
As both downstream collectors of teeing we can use collector mapping() in conjunction with the collector 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
)
));
}
Java 8 - String Keys
Here's a Java 8 compliant code which makes use of three-argument version of collect() and produces the same result as solution with teeing():
public static Map<String, Set<String>> toMessagesByType(List<LoggerMessageDto> loggerMessageList,
String typesKey,
String messagesKey) {
return loggerMessageList.stream()
.collect(
() -> Map.of(
typesKey, new HashSet<>(),
messagesKey, new HashSet<>()
),
(Map<String, Set<String>> map, LoggerMessageDto next) -> {
map.get(typesKey).add(next.getType());
map.get(messagesKey).add(next.getMessage());
},
(left, right) ->
right.forEach((k, v) -> left.get(k).addAll(v))
);
}
Enum
Also, it's worth to mention that enums are more handy and reliable than strings, as @Joop Eggen has point out in the comment. 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.).
Java 8 - Enum & EnumMap
Similarly to the solution shown above, we can use of three-args collect() and provide a prepopulated EnumMap in the supplier.
public static Map<LoggerKeys, Set<String>> toMessagesByType(List<LoggerMessageDto> loggerMessageList) {
return loggerMessageList.stream()
.collect(
() -> EnumSet.allOf(LoggerKeys.class).stream()
.collect(Collectors.toMap(
Function.identity(),
e -> new HashSet<>(),
(v1, v2) -> { throw new AssertionError("duplicates are not expected"); },
() -> new EnumMap<>(LoggerKeys.class)
)),
(Map<LoggerKey, Set<String>> map, LoggerMessageDto next) ->
map.forEach((loggerKey, set) -> set.add(loggerKey.getKey(next))),
(left, right) ->
right.forEach((k, v) -> left.get(k).addAll(v))
);
}
LoggerKey enum having a keyExtractor Function as a property:
public enum LoggerKey {
TYPES(LoggerMessageDto::getType), MESSAGES(LoggerMessageDto::getMessage);
private Function<LoggerMessageDto, String> keyExtractor;
LoggerKeys(Function<LoggerMessageDto, String> keyExtractor) {
this.keyExtractor = keyExtractor;
}
public Function<LoggerMessageDto, String> getKey(LoggerMessageDto dto) {
return keyExtractor.apply(dto);
}
}
enum KeyEnum { TYPES, MESSAGES } and an EnumMapit looks okay.