Search code examples
javalambdamappingjava-streamcollectors

Bad return type in lambda expression String cannot be converted to Object


I need to concat data if duplicates will occur. But can't to do it.

Is it possible to convert String to Object in that case or join that data by another expression?

I have a problem there: (a, b) -> a + ", " + b as Bad return type in lambda expression String cannot be converted to Subject

Map<Student, Subject> result = markList
           .stream()
           .collect(Collectors.toMap(
                  Mark::getStudent,
                  Mark::getSubject,
               (a, b) -> a + ", " + b));

result.forEach((student, subject) -> 
         System.out.printf("%s %s : %s ", student.getFirstName(), 
                   student.getLastName(), subject.getName()));

Here is markList:

List<Mark> markList = new ArrayList<>();
markList.add(new Mark(student_1, subject_1, 1));
markList.add(new Mark(student_1, subject_2, 2));
markList.add(new Mark(student_2, subject_1, 1));

And expect to get joined Subject data in case duplicates would found

Expected Output:

[ student_1 with subject_1, subject_2 and student_2 with subject_1 ]:

John McDonald: Mathematics, Astronomy

Kira Wild: Mathematics

=====================================================

But this code works without duplicates as: (a, b) -> a

Output:

John McDonald: Mathematics

Kira Wild: Mathematics


Solution

  • First things first, (a, b) -> a + ", " + b) will not compile simply because the returned value must be a Subject rather than a String, I believe you have eventually figured this out given your latest edit states the code is now working without duplicates when using the (a, b) -> a merge function.

    The reason why you're not getting the expected result given your latest update is that you're using the merge function (a, b) -> a meaning "if two given Subjects have the same Student then keep the first student and discard the second".

    Instead what you want to do is somehow maintain both students so in essence, you'll have a Map<Student, List<Subject>>.

    This can be done as:

    Map<Student, List<Subject>> result = markList
                    .stream()
                    .collect(Collectors.toMap(
                            Mark::getStudent,
                            v -> new ArrayList<>(singletonList(v.getSubject())),
                            (a, b) -> {a.addAll(b); return a;}));
    

    Actually this is better done via groupingBy with a mapping as downstream collector:

    Map<Student, List<Subject>> resultSet = markList.stream()
                    .collect(groupingBy(Mark::getStudent,
                            mapping(Mark::getSubject, toList())));
    

    and finally you can test the results with:

    resultSet.forEach((student, subjects) ->
                    System.out.printf("%s %s : %s ", student.getFirstName(), student.getLastName(), 
                            subjects.stream().map(Subject::getName).collect(joining(", "))));