domingo, 7 de febrero de 2021

Using flatMap to extract nested elements from a List

Scenario

We need to extract the nested elements from a list of lists based on levels. To do so we can use the flatMap functionality from Java Lambda expressions.

Solution

First we need a data model, in this case we will use a Music Library example based on Bands that have Records that have Songs:

Music Library:

@Getter
@Setter
@ToString
public class MusicLibrary {

    private List<Band> bands;

    public MusicLibrary(List<Band> bands) {
        this.bands = bands;
    }

    public void addBand(Band band){
        if(band != null){
            this.bands.add(band);
        }
    }
}

Band:

@Getter
@ToString
public class Band {

    private final String name;
    private final List<Record> records;

    public Band(String name) {
        this.name = name;
        this.records = new ArrayList<>();
    }

    public void addRecord(Record record){
        if(record != null){
            this.records.add(record);
        }
    }
}

Record:

@Getter
@Setter
@AllArgsConstructor
public class Record {

    private String name;
    private Integer year;
    private List<Song> songs;
}

Song:

@Getter
@Setter
public class Song {

    private String name;

    public Song(String name) {
        this.name = name;
    }
}

A quick Music Library for tests We will have a simple music library formed by two bands (Nirvana and Soundgarden) with 3 records and 9 songs in total:

    private MusicLibrary musicLibraryTest(){

        Band nirvana = new Band("Nirvana");

        Record bleach = new Record(
                "Bleach",
                1989,
                List.of(
                        new Song("Love Buzz"),
                        new Song("Negative Creep"))
        );

        Record nevermind = new Record(
                "Nevermind",
                1991,
                List.of(
                        new Song("Smells like teen spirit"),
                        new Song("Lithium"),
                        new Song("Something in the way"))
        );

        nirvana.addRecord(bleach);
        nirvana.addRecord(nevermind);

        Band soundgarden = new Band("Soundgarden");

        Record superunknown = new Record(
                "Superunknown",
                1994,
                List.of(
                        new Song("Fell on black days"),
                        new Song("Black hole sun"),
                        new Song("The day I tried to live"),
                        new Song("4th of July")
                )
        );

        soundgarden.addRecord(superunknown);

        return new MusicLibrary(List.of(nirvana, soundgarden));

    }

Now, let's see how to use the flatMap method from Java Lambda streams that will allow us to access to the different levels of the nested objects in our data structure:

Accessing to first level Records

@Test
void extractRecords(){

    List<Record> records = musicLibraryTest().getBands()
            .stream()
            .flatMap(band -> band.getRecords().stream())
            .collect(Collectors.toList());

    Assertions.assertThat(records).size().isEqualTo(3);
    System.out.println(records.toString());
}

The output will be:

[
     Record{
        Bleach',
        1989,
        [
            Love Buzz, 
            Negative Creep]
    }, 
     Record{
        Nevermind',
        1991,
        [
            Smells like teen spirit, 
            Lithium, 
            Something in the way]
    }, 
     Record{
        Superunknown',
        1994,
        [
            Fell on black days, 
            Black hole sun, 
            The day I tried to live, 
            4th of July]
    }]

Accessing to the second level Songs

To get all the songs of our library we can use again the flatMap method to access to the second level of nested data in our structure:

@Test
void extractSongs(){

    List<Song> songs = musicLibraryTest().getBands()
            .stream()
            .flatMap(band -> band.getRecords().stream())
            .flatMap(record -> record.getSongs().stream())
            .collect(Collectors.toList());

    Assertions.assertThat(songs).size().isEqualTo(9);
    System.out.println(songs);
}

The output will show the complete list of songs of our library:

[
            Love Buzz, 
            Negative Creep, 
            Smells like teen spirit, 
            Lithium, 
            Something in the way, 
            Fell on black days, 
            Black hole sun, 
            The day I tried to live, 
            4th of July
]

domingo, 31 de enero de 2021

Convert HashMap objects using Lambda expression

Scenario

We need to transform a HashMap that contains a String and an object into another HashMap that keeps that information but mapping the object to a different one. Let's imagine that we have a set of Person entities included in a HashMap and we want at some point to transform them into the equivalent DTOs, to do so we can use a simple Lambda expression to get the work done:

Solution

A simple Person entity:

@Getter
@AllArgsConstructor
public class Person {

    private final String name;
    private final String lastName;
    private final int age;
    private final String address;

    public PersonDTO toDto(){
        return new PersonDTO(name, lastName, age, address);
    }
}

Our target DTO

@Getter
@AllArgsConstructor
public class PersonDTO {

    private final String name;
    private final String lastName;
    private final int age;
    private final String address;
}

Now, to go through the a set of Person included in a HashMap and transform them into the correspondent DTOs we can use the toMap included with the java.util.stream.Collectors and define the value for both the key and the value using our toDTO() method to transform each entity.

Map<String, PersonDTO> personDTOMap = personMap.entrySet()
        .stream()
        .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().toDto()));

In a complete example:

@Test
void map_a_Map_to_a_Map_and_dont_get_mad(){
    Map<String, Person> personMap = new HashMap<>();
    personMap.put("01", new Person("John", "Doe", 41, "Baker Street"));
    personMap.put("02", new Person("Rose", "Smith", 39, "Helm Street"));

    Map<String, PersonDTO> personDTOMap = personMap.entrySet()
            .stream()
            .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().toDto()));

    Assertions.assertThat(personDTOMap).size().isEqualTo(2);
    Assertions.assertThat(personDTOMap.entrySet()
            .stream()
            .filter(entry -> entry.getKey().equals("01"))
            .findFirst()
            .orElseThrow()
            .getValue()
            .getName())
            .isEqualTo("John");
}

Using flatMap to extract nested elements from a List

Scenario We need to extract the nested elements from a list of lists based on levels. To do so we can use the flatMap functionality from ...