Home > front end >  Generate Map using list of list using stream in Java 8
Generate Map using list of list using stream in Java 8

Time:10-28

I have the following domain classes Trip and Employee:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Trip {
    private Date startTime;
    private Date endTime;
    List<Employee> empList;
    
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
    private String name;
    private String empId;
}

I have a list of Trip instances. And I want to create a map of type Map<String,List<Trip>> associating id of each employee empId with a list of trips using Stream API.

Here's my attempt:

public static void main(String[] args) {
    
    List<Trip> trips = new ArrayList<>();
    Map<Stream<String>, List<Trip>> x = trips.stream()
        .collect(Collectors.groupingBy(t -> t.getEmpList()
            .stream().map(Employee::getEmpId)
        ));
}

How can I generate the map of the required type?

When the type of map is Map<String,List<Trip>> it gives me a compilation error:

Unresolved compilation problem: Type mismatch:
cannot convert from Map<Object,List<Trip>> to Map<String,List<Trip>>

CodePudding user response:

To group the data by the property of a nested object and at the same time preserve a link to the enclosing object, you need to flatten the stream using an auxiliary object that would hold references to both employee id and enclosing Trip instance.

A Java 16 record would fit into this role perfectly well. If you're using an earlier JDK version, you can implement it a plain class (a quick and dirty way would be to use Map.Entry, but it decreases the readability, because of the faceless methods getKey() and getValue() require more effort to reason about the code). I will go with a record, because this option is the most convenient.

The following line is all we need (the rest would be automatically generated by the compiler):

public record TripEmployee(String empId, Trip trip) {}

The first step is to flatten the stream data and turn the Stream<Trip> into Stream<TripEmployee>. Since it's one-to-many transformation, we need to use flatMap() operation to turn each Employee instance into a TripEmployee.

And then we need to apply collect. In order to generate the resulting Map, we can make use of the collector groupingBy() with collector mapping() as a downstream. In collector mapping always requires a downstream collector and this case we need to provide toList().

List<Trip> trips = // initializing the list
        
Map<String, List<Trip>> empMap = trips.stream()
    .flatMap(trip -> trip.getEmployee().stream()
        .map(emp -> new TripEmployee(emp.getEmpId(), trip))
    )
    .collect(Collectors.groupingBy(
        TripEmployee::empId,
        Collectors.mapping(TripEmployee::trip,
            Collectors.toList())
    ));

CodePudding user response:

Not sure which Java version you are using but since you have mentioned Stream, I will assume Java 8 at least.

Second assumption, not sure why but looking at your code (using groupingBy ) you want the whole List<Trip> which you get against an empId in a Map.

To have the better understanding first look at this code (without Stream):

public Map<String, List<Trip>> doSomething(List<Trip> listTrip) {

    List<Employee> employeeList = new ArrayList<>();
    for (Trip trip : listTrip) {
        employeeList.addAll(trip.getEmployee());
    }

    Map<String, List<Trip>> stringListMap = new HashMap<>();

    for (Employee employee : employeeList) {
        stringListMap.put(employee.getEmpId(), listTrip);
    }
    return stringListMap;
}

You can see I pulled an employeeList first , reason being your use case. And now you can see how easy was to create a map out of it. You may use Set instead of List if you're worried about the duplicates.

So with StreamApi above code could be:

   public Map<String, List<Trip>> doSomethingInStream(List<Trip> listTrip) {

        List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());

        return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip));
    }

You can take care of duplicates while creating map as well, as:

public Map<String, List<Trip>> doSomething3(List<Trip> listTrip) {

    List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());

    return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip, (oldValue, newValue) -> newValue));
}

Like the first answer says, if you are Java 16 using record will ease your task a lot in terms of model definition.

  • Related