javaforeachjava-8java-stream

Replace enhanced for loops over nested lists with streams


I have some objects which include nested lists. I'd typically use nested for loops to carry out any transformation on these, but I'm keen to explore Java 8 streams. Essentially I'm trying to create an output object which will be composed of fields accessed within each nested list.

I've shared a really simple example below, and how I would typically carry this out with enhanced for loops. Could anyone share with me how I should be doing this will streams? Also, if we were to assume that these some of these lists will have a cardinality of [0:M], i.e. they are optional lists, how would I make the stream null-safe?

// Objects below
class Qualification {
    String qualificationName;
    String qualificationValue;
}

class Person {
    String name;
    List<Qualification> qualifications;
}

class Group {
    String groupId;
    List<Person> people;
}

class Output {
    String groupId;
    String name;
    String qualificationName;
    String qualificationValue;
}

Below is how I would typically carry out this transformation.

// Create empty list to hold the output objects    
List<Output> outputList = new ArrayList<Output>();
for(Group g : groups) {

    for (Person p : g.getPeople()) {

        for (Qualification q : p.getQualifications()) {
           // Compose new object and add to list

            Output output = new Output();
            output.setId(g.getId());
            output.setName(p.getName());
            output.setQualificationValue(q.getQualificationValue());
            output.setQualificationName(q.getQualificationName());
                    
            outputList.add(output);
        }
    }
}

Solution

  • You will have to use

    (I have used Lombok annotations; you may use regular getters/setters and constructors.)

    public class NestedForLoopsIntoStreams{
        @NoArgsConstructor @AllArgsConstructor( staticName = "of" )
        @Getter @Setter
        private static class Qualification {
            String qualificationName;
            String qualificationValue;
        }
    
        @NoArgsConstructor @AllArgsConstructor( staticName = "of" )
        @Getter @Setter
        private static class Person {
            String name;
            List<Qualification> qualifications;
        }
    
        @NoArgsConstructor @AllArgsConstructor( staticName = "of" )
        @Getter @Setter
        private static class Group {
            String groupId;
            List<Person> people;
        }
    
        @NoArgsConstructor @AllArgsConstructor( staticName = "of" )
        @Getter @Setter @ToString
        private static class Output {
            String groupId;
            String name;
            String qualificationName;
            String qualificationValue;
        }
        
        public static void main( String[] args ){
            List<Group> groups = testData();
            List<Output> outputs = 
            groups.stream().filter( g -> g.getPeople() != null )
                .flatMap( g -> {
                    return g.getPeople().stream().filter( p -> p.getQualifications() != null )
                        .flatMap( p -> {
                            return p.getQualifications().stream().map( q ->
                                Output.of( g.groupId, p.name, q.qualificationName, q.qualificationValue )
                            );
                        }
                );
            } ).collect( Collectors.toList() );
            
            outputs.forEach( System.out::println );
        }
    
        private static List<Group> testData(){
            return Arrays.asList(
                Group.of( "1", Arrays.asList( 
                    Person.of( "Person1", Arrays.asList( Qualification.of( "Engg", "BTech" ) ) ),
                    Person.of( "Person2", Arrays.asList( Qualification.of( "Engg", "MTech" ) ) )
                ) ),
                Group.of( "2", Arrays.asList( 
                    Person.of( "Person3", Arrays.asList( Qualification.of( "Engg", "BTech" ) ) )
                ) ),
                /* Person with no qualication */
                Group.of( "3", Arrays.asList(
                    Person.of( "Person4", Arrays.asList( Qualification.of( "Engg", "BTech" ) ) ),
                    Person.of( "Person5", null )
                ) ),
                /* No people */
                Group.of( "4", null ) 
            );
        }
    }
    

    Running this will give this output:

    NestedForLoopsIntoStreams.Output(groupId=1, name=Person1, qualificationName=Engg, qualificationValue=BTech)
    NestedForLoopsIntoStreams.Output(groupId=1, name=Person2, qualificationName=Engg, qualificationValue=MTech)
    NestedForLoopsIntoStreams.Output(groupId=2, name=Person3, qualificationName=Engg, qualificationValue=BTech)
    NestedForLoopsIntoStreams.Output(groupId=3, name=Person4, qualificationName=Engg, qualificationValue=BTech)