0

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>>
7
  • i tried i got Map<Stream<String>,List<Trip>> but i want something like Map<String,List<Trip>> Commented Oct 27, 2022 at 18:59
  • Map<Stream<String>, List<Trip>> empMap = trip.stream().collect( Collectors.groupingBy(t -> t.getEmployee().stream().map(EMPLOYEE::getempid))) Commented Oct 27, 2022 at 19:05
  • I posted the code Commented Oct 27, 2022 at 19:36
  • Better, instead of image, compile the code and post the compilation error as a text. See Why should I not upload images of code/data/errors when asking a question? Commented Oct 27, 2022 at 19:41
  • Exception in thread "main" java.lang.Error: Unresolved compilation problem: Type mismatch: cannot convert from Map<Object,List<Trip>> to Map<String,List<Trip>> Commented Oct 27, 2022 at 20:04

3 Answers 3

2

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.getEmpList().stream()
        .map(emp -> new TripEmployee(emp.getEmpId(), trip))
    )
    .collect(Collectors.groupingBy(
        TripEmployee::empId,
        Collectors.mapping(TripEmployee::trip,
            Collectors.toList())
    ));

A Java 8 compliant solution is available via this Link

Sign up to request clarification or add additional context in comments.

Comments

0

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.

3 Comments

Yeah I am using java 8 and trying to group whole List<Trip> which i get against the empid in a Map.
@Jha - hope the answer helps then.
@Jha : I noticed you accepted the answer but then unaccepted it, did it not work? Anything you're stuck at, let me know if I can help?
0

Using Java 8 stream

You can use the below approach to get the desired results using stream function groupingBy. Since you have mentioned above to use java 8, so my solution is inclined to java 8 itself.

Logic:

Here,

  • First I have created an additional list of EmployeeTripMapping object with Trip data corresponding to the empId by iterating the listOfTrips.
  • I have used Collectors.groupingBy on the List<EmployeeTripMapping> and grouped the data based on the empId and using Collectors.mapping collect the list of Trip corresponding to the empId.

Few Suggestions:

  1. Records in java 14 : As I can see in your problem statement, you are using lombok annotations to create getters, setters and constructors, so instead of that we can replace our data classes with records. Records are immutable classes that require only the type and name of fields. We do not need to create constructor, getters, setters, override toString() methods, override hashcode and equals methods. Here

  2. JavaTimeAPI in java 8: Instead of Date, you can use LocalDateTime available in java time API in java 8. Here

Code:

    public class Test {
        public static void main(String[] args) {

        Trip t1 = new Trip(LocalDateTime.of(2022,10,28,9,00,00),
                  LocalDateTime.of(2022,10,28,18,00,00),
                  Arrays.asList(new Employee("emp1","id1")));
        Trip t2 = new Trip(LocalDateTime.of(2021,10,28,9,00,00),
                  LocalDateTime.of(2021,10,28,18,00,00),
                  Arrays.asList(new Employee("emp1","id1")));
        Trip t3 = new Trip(LocalDateTime.of(2020,10,28,9,00,00),
                  LocalDateTime.of(2020,10,28,18,00,00),
                  Arrays.asList(new Employee("emp2","id2")));
        Trip t4 = new Trip(LocalDateTime.of(2019,10,28,9,00,00),
                  LocalDateTime.of(2019,10,28,18,00,00),
                  Arrays.asList(new Employee("emp2","id2")));

        List<Trip> listOfTrips = Arrays.asList(t1,t2,t3,t4);

        List<EmployeeTripMapping> empWithTripMapping = new ArrayList<>();
        listOfTrips.forEach(x -> x.getEmpList().forEach(y ->
                empWithTripMapping.add(new EmployeeTripMapping(y.getEmpId(),x))));

        Map<String,List<Trip>> employeeTripGrouping = empWithTripMapping.stream()
            .collect(Collectors.groupingBy(EmployeeTripMapping::getEmpId,
                        Collectors.mapping(EmployeeTripMapping::getTrip,
                                                 Collectors.toList())));
        System.out.println(employeeTripGrouping);
    }
}

EmployeeTripMapping.java

public class EmployeeTripMapping {

    private String empId;
    private Trip trip;

    //getters and setters
}

Output:

{emp2=[Trip{startTime=2020-10-28T09:00, endTime=2020-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}, Trip{startTime=2019-10-28T09:00, endTime=2019-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}], 
 emp1=[Trip{startTime=2022-10-28T09:00, endTime=2022-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}, Trip{startTime=2021-10-28T09:00, endTime=2021-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}]}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.