0

Let's say you have an interface, a couple of implementation classes and an Enum like this: Interface:

public interface Employee {
    // No two employees will have same favorite hobbies
    List<Hobby> favoriteHobbies; 
}

Implementations:

public class Employee1 implements Employee {
    @Override
    public List<Hobby> favoriteHobbies {
        return List.of(Hobbies.swimming, Hobbies.dancing);
    }
}

public class Employee2 implements Employee {
    @Override
    public List<Hobby> favoriteHobbies {
        return List.of(Hobbies.running);
    }
}

Enum:

public enum Hobby {
    swimming,
    running,
    dancing
}

I have List<Employee> employees = List.of(Employee1, Employee2);

And using Streams, I want to Stream through employees list and again Stream through each Employee object's favoriteHobbies() and create a Map<Hobby, Employee> like below:

"swimming" -> "Employee1"
"dancing" -> "Employee1"
"running" -> "Employee2"
Naman
  • 27,789
  • 26
  • 218
  • 353
AMagic
  • 2,690
  • 3
  • 21
  • 33

2 Answers2

1

I would create a Map.Entry in a Stream and then collect it using its keys and values

Since this is possible using Stream#mapMulti

Map<Hobby, Employee> map = employees.stream()
        .mapMulti((Employee employee, Consumer<AbstractMap.SimpleEntry<Hobby, Employee>> consumer) -> {
            employee.favoriteHobbies().forEach(hobby -> {
                consumer.accept(new AbstractMap.SimpleEntry<>(hobby, employee));
            });
        })
        .collect(Collectors.toMap(
                AbstractMap.SimpleEntry::getKey,
                AbstractMap.SimpleEntry::getValue,
                (entryOne, entryTwo) -> entryOne // or whatever, maybe even throw new XXXException()
        ));

Before this is possible with a Stream#flatMap and Collectors#toMap

Map<Hobby, Employee> map = employees.stream()
        .flatMap(employee -> employee.favoriteHobbies()
                .stream()
                .map(hobby -> new AbstractMap.SimpleEntry<>(hobby, employee)))
        .collect(Collectors.toMap(
                AbstractMap.SimpleEntry::getKey,
                AbstractMap.SimpleEntry::getValue,
                (entryOne, entryTwo) -> entryOne // or whatever, maybe even throw new XXXException()
        ));

Which produces

enter image description here

Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
0

Since Java 9 it is possible to use Map.entry, also a LinkedHashMap should be constructed to maintain insertion order of the map entries:

Map<Hobby, Employee> map = employees()
    .stream()
    .flatMap(e -> e.favoriteHobbies().stream().map(h -> Map.entry(h, e)))
    .collect(Collectors.toMap(
        Map.Entry::getKey, 
        Map.Entry::getValue, 
        (a, b) -> a, 
        LinkedHashMap::new
    ));

However, if the input employees list contains multiple instances of different implementations, it would make more sense to build a map Map<Hobby, List<Employee>> using Stream::flatMap, Collectors::groupingBy, Collectors::mapping, Collectors::toList:

List<Employee> employeeList = List.of(
    new Employee1("John"), new Employee1("Jack"), new Employee2("Jeff"),
    new Employee1("Jake"), new Employee2("Jean")
);
Map<Hobby, List<Employee>> mapList = employees()
    .stream()
    .flatMap(e -> e.favoriteHobbies().stream().map(h -> Map.entry(h, e)))
    .collect(Collectors.groupingBy(
        Map.Entry::getKey, 
        LinkedHashMap::new, 
        Collectors.mapping(Map.Entry::getValue, Collectors.toList())         
    ));
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42