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);
}
}
}
You will have to use
filter()
to take care of the null lists, ANDflatMap()
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)