Search code examples
javafunctional-programmingjava-streamcollectors

Searching for JDK equivalent for transforming while collecting stream


Problem

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.

My own solution

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)));

Question

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.


Solution

  • 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())));