Home > Software design >  Java replace enhanced for loop with stream accessing elements from each stream
Java replace enhanced for loop with stream accessing elements from each stream

Time:12-04

first time question so bare with me..

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 Java8 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? Thanks

    // 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);
                }
            }
        }

CodePudding user response:

You will have to use

  • filter() to take care of the null lists, AND
  • flatMap() to create substreams from internal lists

(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)

CodePudding user response:

You can do something like this:

    List<Output> outputList = groups.stream()
            .flatMap(g -> g.getPeople().stream()
                    .flatMap(p ->
                            p.getQualifications()
                                    .stream()
                                    .map(q -> {
                                        Output output = new Output();
                                        output.setGroupId(g.getGroupId());
                                        output.setName(p.getName());
                                        output.setQualificationValue(q.getQualificationValue());
                                        output.setQualificationName(q.getQualificationName());
                                        return output;
                                    })
                    ))
            .collect(Collectors.toList());
  • Related