I have a List
of Request
objects, where each request contains the origin host and the requested URL. I want to transform this into Map<String, Set<String>>
, where the keys are the hosts and the values are the unique URLs that have been called by the host.
I found that you could use Collectors.groupingBy
to transform a List
into a Map
. This seemed like the method for the job, but it didn't fit my total needs. I had to create an extra function:
private static <T, K> Collector<T, ?, Set<K>> toSet(Function<? super T, ? extends K> mapper) {
return Collector.of(
HashSet::new,
(list, item) -> list.add(mapper.apply(item)),
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
);
}
Using this function, I could do it like this:
Map<String, Set<String>> hostUrls = getRequests().stream()
.map(r -> new ImmutablePair<>(r.getOriginHost(), r.getRequestUrl()))
.collect(Collectors.groupingBy(ImmutablePair::getLeft, toSet(ImmutablePair::getRight)));
Is there some way to do this transforming without creating this extra toSet
function?
My wish doesn't seem that extreme. I want some extra kind of mapping before collecting everything into one Set
(could also be any other kind of collection of course). I have the feeling that I am missing some function, but I cannot see which.
You don't need to define any extra method. You can perform Collectors.mapping
after groupingBy
as below.
Collectors.mapping
has two arguments. first is a mapper and the second is downstream.
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
Collector<? super U, A, R> downstream)
By using toSet
downstream you can complete your task.
Map<String, Set<String>> map = getRequests().stream()
.collect(Collectors.groupingBy(Request::getOriginHost,
Collectors.mapping(Request::getRequestUrl, Collectors.toSet())));