Frakcool
Frakcool

Reputation: 11163

Map an object with multiple properties to another and then collect to list

I have an object like this one:

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private String lastName;
}

And another one, let's call it:

@Data
@AllArgsConstructor
@NoArgsConstructor
class DBPerson {
    private String name;
    private String lastName;
}

Assume both objects have a constructor with name and lastName and their respective getters and setters, I'm using lombok

List<DBPerson> dbPersons = new ArrayList();
dbPersons.add(new DBPerson("Harry", "Potter"));
dbPersons.add(new DBPerson("Hermione", "Granger"));
dbPersons.add(new DBPerson("Ronald", "Weasley"));

I need to convert from a List<DBPerson> to List<Person>, so I tried this way:

dbPersons.stream()
.map(DBPerson::getName)
.map(Person::new)
.collect(Collectors.toList());

This obviously is giving me an error on new because there's no constructor with a single String on Person class

How could I map both properties of DBPerson to create a new Person and then collect all of those into a List<Person>?

I've tried with .forEach, however I'm not sure it would be the most optimal solution, so if there's another way of doing it using Stream API I'll appreciate any better solution, otherwise I'll have to stick with .forEach.

I've found other solutions using flatMap but those are for concatenating both name and lastName into a single String, which is not what I want.

Upvotes: 0

Views: 7933

Answers (2)

navnath
navnath

Reputation: 3714

In java 8

    Function<DBPerson, Person> toPersonMapper =
      dbPerson -> new Person(dbPerson.getName(), dbPerson.getLastName());

    List<Person> personList =
      dbPersonList
        .stream()
        .map(toPersonMapper)
        .collect(Collectors.toList());

Upvotes: 2

Basil Bourque
Basil Bourque

Reputation: 340098

tl;dr

Add to the second class a constructor that takes an argument of the type of the first class. Use Stream#map with a method reference for that constructor, and collect new instances of the second class.

personXes.stream().map( PersonY :: new ).toList();

Lambda function

Rather than use method references for your getters, write a lambda function to produce a new object.

Let’s use the Java 16+ records feature for brevity.

package work.basil.mix;

public record PersonX( String firstName , String lastName ) { }
package work.basil.mix;

public record PersonY( String firstName , String lastName ) { }

Get some sample data.

List < PersonX > xList =
        List.of(
                new PersonX( "Harry" , "Potter" ) ,
                new PersonX( "Hermione" , "Granger" ) ,
                new PersonX( "Ronald" , "Weasley" )
        );

Make a stream of the elements in that list. For each of those PersonX objects, we need to generate a PersonY object. For such a transformation, use Stream#map. The result of our map function must match the type of our new collection. In our case that new collection is a List< PersonY >. So our transformational map function must produce a PersonY object.

Lastly, we collect each newly produced PersonY into a unmodifiable list by calling .toList. Before Java 16, you must use the more verbose .collect( Collectors.toList() ).

List < PersonY > yList = xList.stream().map( personX -> new PersonY( personX.firstName() , personX.lastName() ) ).toList() ;

In alternate formatting.

List < PersonY > yList =
        xList
                .stream()
                .map(
                        personX -> new PersonY( personX.firstName() , personX.lastName() )
                )
                .toList();

When run.

yList = [PersonY[firstName=Harry, lastName=Potter], PersonY[firstName=Hermione, lastName=Granger], PersonY[firstName=Ronald, lastName=Weasley]]

Alternate constructor

Another approach is to add a constructor to your second class, taking as an argument an object of the first class. The constructor then extracts the necessary data from the existing object of the first class to construct an object of the second class.

This approach makes sense if you will be doing this conversion often, in an on-going manner.

package work.basil.mix;

public record PersonY( String firstName , String lastName ) {
    public PersonY ( PersonX personX ) {
        this( personX.firstName() , personX.lastName() );
    }
}

Now we can simplify our code seen above.

List < PersonY > yList = xList.stream().map( personX -> new PersonY( personX ) ).toList();

We can further simplify by using a method reference for the constructor: PersonY :: new. Each personX object will be implicitly passed to the constructor.

This next line of code works exactly the same as the line above, just shorter, not better. In both cases, each streamed object of the first class is being passed to the constructor of the second class.

List < PersonY > yList = xList.stream().map( PersonY :: new ).toList();

Upvotes: 7

Related Questions