Home > Enterprise >  Filtering of nested lists with Java Streams
Filtering of nested lists with Java Streams

Time:03-30

I have a to filter on some nested Lists. Consider a List<A> which contains List<B> which contains List<C>. I need to filter, to return all the A Objects which contain at least one B which satisfies a given condition, and at least one C which satisfies a given condition.

I've got a contrived example below, which basically is doing what I've tried to implement in my actual example. Take a list of schools in a city Each school has many subjects, and each subject has many teachers. I want to retain a list of schools which has subject.subjectName = 'Math' and at least one teacher within this subject where teacher.age > 65.

I have implemented this using a custom predicate, so I've also made a this in my example. I'm getting an error where 'cannot convert from boolean to Stream' but honestly, I'm struggling in this scenario.

    @Getter
    @Setter
    class School {
        private String schoolId;
        private List<Subject> classes;
        
    }
        
    @Getter
    @Setter
    class Subject {
        String subjectName;
        List<Teacher> teachers;
    }
    
    @Getter
    @Setter
    class Teacher {
        private String teacherName;
        private Integer age;
    }

    public class TestClass {
        public static void main( String[] args ){
            List<School> schools;
            
            // Get All schools where A mathTeacher is over retirement age
            List<School> schoolsWithRetirementAgeMathTeachers = schools.stream()
                    .filter(school -> null != school.getClasses())
                    .flatMap(school -> {
                        return school.getClasses().stream()
                                .filter(subject -> subject.getSubjectName().equalsIgnoreCase("Math"))
                                .filter(subject -> null != subject.getTeachers())
                                .flatMap(subject -> {
                                    return subject.getTeachers().stream()
                                            .filter(teacher -> teacher != null)
                                            .anyMatch(isRetirementAge);
                                });
                    }).collect(Collectors.toList());
                    
        }
                
        public static Predicate<Teacher> isRetirementAge = teacher -> teacher.getAge() > 65;
    
    }

CodePudding user response:

If you're trying to return the parent objects, you don't want to use flatMap(). You just need to nest some anyMatch() calls:

List<A> filtered = listA.stream()
        .filter(a -> a.getListB()
                .stream()
                .anyMatch(b -> testB(b) && b.getListC()
                        .stream()
                        .anyMatch(c -> testC(c))))
        .collect(Collectors.toList());

CodePudding user response:

Since you are looking for Schools as an output, you do not need to map/flatmap your stream into another object. The filtering capability has to be smart enough to go through the nested collections and confirm if there was anyMatch based on your requirements.

Primarily in the below code, we filter all those schools where a subject by the name "Math" is present and one of the teachers teaching Maths is above the mentioned retirement age.

// Get All schools where a Math teacher is over retirement age
List<School> schoolsWithRetirementAgeMathTeachers = schools.stream()
        .filter(school -> school.getSubjects()
                .stream()
                .anyMatch(subject ->
                        subject.getName().equalsIgnoreCase("Math") &&
                                subject.getTeachers()
                                        .stream()
                                        .anyMatch(isRetirementAge)
                ))
        .collect(Collectors.toList());
  • Related