Reputation: 30067
I want to create a new Child
instance passing a Parent
and other additional parameters.
For example if I have:
public class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
}
public class Child extends Parent {
public String subValue;
}
With lombok, is there a builder that lets me create a Child
instance passing the Parent
and the missing value as parameters?
Would be easier if I could write something like:
Parent p = Parent.builder()
.param1("a")
.param2("b")
// many parameters
.paramN("b")
.build();
Child c = Child.builder(p).subValue("c").build();
Upvotes: 8
Views: 10069
Reputation: 71
Have found simple workaround. You will need @SuperBuilder(toBuilder = true) annotation in parent and child classes. SuperBuilder provides constructor with builder class as a parameter, so we can use it in child class for doing a copy. This means that you don't need to write mapping manually at all.
@SuperBuilder(toBuilder = true)
public class Parent {
private String field;
}
@Data
@SuperBuilder(toBuilder = true)
public class Child extends Parent {
private String extraField;
public Child(Parent instance) {
super(instance.toBuilder());
}
public static ChildBuilder<?, ?> builder(Parent instance) {
return new Child(instance).toBuilder();
}
}
As a result you can copy fields from parent instance to child builder and supplement with needed data:
Child result = Child.builder(parentInstance)
.extraField("extra")
.build();
Upvotes: 2
Reputation: 1
Another approach that could be useful for those following this thread is to utilize a mapping library like MapperStruct. With MapperStruct, you can define a mapper interface as follows:
@Mapper
public interface PetMapper {
Dog toDog(Pet pet);
}
After defining the mapper, you can use it in your code like this:
private static final PetMapper mapper;
...
public void myFunction(Pet pet) {
mapper.toDog(pet).toBuilder().id("My ID").build();
}
or
private static final PetMapper mapper;
...
public void myFunction(Pet pet) {
mapper.toDog(pet).setId("My ID");
}
This way, you can easily map between different objects and modify the resulting object as needed.
The idea here is to let the mapper handle the creation of a new object child from the parent class (or from its sibling classes), and then using that mapped object you can use its setters to set custom properties or you can use toBuilder() (Be aware that the toBuilder will create a new object once you call .build()).
Upvotes: 0
Reputation: 8052
The regular @Builder
is not sufficient here, because you are dealing with a class hierarchy. However, @SuperBuilder
was made exactly for such a case.
@SuperBuilder
generates complex code loaded with generics. That makes this solution difficult to understand without in-depth knowledge about the code @SuperBuilder
generates. You should think about whether this is worth it.
Here's the solution (with Lombok >= 1.18.16):
@SuperBuilder(toBuilder = true)
public static class Parent {
public String param1;
public String param2;
// many parameters
public String paramN;
public abstract static class ParentBuilder<C extends Parent, B extends Parent.ParentBuilder<C, B>> {
protected B $fillValuesFromParent(Parent instance) {
$fillValuesFromInstanceIntoBuilder(instance, this);
return self();
}
}
}
@SuperBuilder(toBuilder = true)
public static class Child extends Parent {
public String subValue;
public static ChildBuilder<?, ?> toBuilder(Parent p) {
return new ChildBuilderImpl().$fillValuesFromParent(p);
}
}
The new toBuilder
method on Child
creates a new ChildBuilderImpl
(which will create a Child
instance when calling build()
). To fill the values from the given Parent p
, it calls the new $fillValuesFromParent
method from ParentBuilder
. This method further delegates the call to the method $fillValuesFromInstanceIntoBuilder
, which is generated by Lombok and performs the actual copying of the field values to the new builder instance.
Also note the $
prefix on the methods. This basically says: I'm an implementation detail; don't use me unless you know what you are doing, I might break on the next Lombok version without further notice.
Upvotes: 8
Reputation: 3735
Other answers don't truly make your client code simply reuse the parent instance you already have. But this is doable. You have two options:
The hard one is to write your custom annotation that does what you want. You can even make it generic so that it works for any classes the have parent/child hierarchy. Have a look at this example. If you feel brave you can raise a feature request on Lombok's github page.
Option two would be to write your custom builder for the child. See example here. In your custom builder in the init step you would be reading a passed in Parent instance, and setup the inherited fields only.
Upvotes: 2