0
public class Student {
    String name;
    int age;
}

I have a list of Student objects and I need to group them according to specific logic:

  1. Group all students whose name starts with "A"
  2. Group all students whose name starts with "P"
  3. Group all students whose age is greater than or equal to 30

So far I what I have done:

List<Student> students = List.of(
     new Student("Alex", 31), 
     new Student("Peter", 33), 
     new Student("Antony", 32),
     new Student("Pope", 40), 
     new Student("Michel", 30));

Function<Student, String> checkFunction = e -> {
    if (e.getName().startsWith("A")) {
        return "A-List";
    } else if (e.getName().startsWith("P")) {
        return "P-List";
    } else if (e.getAge() >= 30) {
        return "30's-List";
    } else {
        return "Exception-List";
    }
};

Map<String, List<Student>> result = students.stream().collect(Collectors.groupingBy(checkFunction));

for (var entry : result.entrySet()) {
    System.out.println(entry.getKey() + "---");
    for (Student std : entry.getValue()) {
        System.out.println(std.getName());
    }
}

output

A-List---
Alex
Antony
P-List---
Peter
Pope
30's-List---
Michel

I understand this logic what I am following is wrong, that is why the 30's list is not populated correctly. Is it really possible with groupingBy()?

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
User_1940878
  • 321
  • 1
  • 6
  • 25
  • 2
    `groupingBy` will put each student into a group. It can't put them into multiple groups if they match multiple criteria. How can it, right? `checkFunction` returns a single `String` group name, not a list of names. – John Kugelman Jan 26 '22 at 15:27
  • Yes @JohnKugelman I understood. It is very much possible by iterating the List 3 times and fetch data individually. I was attempting an optimal solution – User_1940878 Jan 26 '22 at 16:02
  • Do you need to build the per-group lists? The most direct approach is to iterate over `students` once, check each student against the three conditions, and do the applicable actions, without building up any sub-lists. – John Kugelman Jan 26 '22 at 16:06
  • 1
    Does this answer your question? [Java 8 group by String](https://stackoverflow.com/questions/50411257/java-8-group-by-string) – Didier L Jan 26 '22 at 16:06
  • Also [Java stream and groupping: put object to multiple groups](https://stackoverflow.com/q/59968420/525036), which is also a duplicate of the former. – Didier L Jan 26 '22 at 16:07
  • @DidierL I did something like, Map> result = students.stream() .collect(Collectors.groupingBy(checkFunction, Collectors.mapping(e -> e, Collectors.toList()))); but same result! In my case, there is no collection of data inside the object. So I think flatmap is not relevant here – User_1940878 Jan 26 '22 at 17:27
  • Thanks @DidierL that solves my case :-) – User_1940878 Jan 26 '22 at 18:13

1 Answers1

1

This can be handled like in Java 8 group by String but you will have to adapt checkFunction to actually return the groups of each Student.

private Stream<String> mapToGroups(Student e) {
    Builder<String> builder = Stream.builder();
    boolean isException = false;
    if (e.getName().startsWith("A")) {
        builder.add("A-List");
    } else if (e.getName().startsWith("P")) {
        builder.add("P-List");
    } else {
        isException = true;
    }
    if (e.getAge() >= 30) {
        builder.add("30's-List");
    } else if (isException) {
        builder.add("Exception-List");
    }
    return builder.build();
}

However, if we were to use this function in a flatMap() call, we would loose the Student in the process. So what we really want is having this method return String<Map.Entry<String, Student>> so that we can later user the key for grouping and the value for collecting the groups:

private Stream<Entry<String, Student>> mapToGroupEntries(Student e) {
    Builder<Entry<String, Student>> builder = Stream.builder();
    boolean isException = false;
    if (e.getName().startsWith("A")) {
        builder.add(new SimpleEntry<>("A-List", e));
    } else if (e.getName().startsWith("P")) {
        builder.add(new SimpleEntry<>("P-List", e));
    } else {
        isException = true;
    }
    if (e.getAge() >= 30) {
        builder.add(new SimpleEntry<>("30's-List", e));
    } else if (isException) {
        builder.add(new SimpleEntry<>("Exception-List", e));
    }
    return builder.build();
}

We can now use this function as part of a flatMap() call to convert our Stream<Student> to a Stream<Entry<String, Student>> and then group them:

Map<String, List<Student>> result = students.stream()
        .flatMap(s -> mapToGroupEntries(s))
        .collect(Collectors.groupingBy(Entry::getKey,
                Collectors.mapping(Entry::getValue, Collectors.toList())));
Didier L
  • 18,905
  • 10
  • 61
  • 103