Lombok #Builder inheritance workaround - java

Lombok #Builder doesn't work for inheritance use cases:
For example
class Foo{
protected int xyz1;
.....
protected String xyz7;
}
class Bar extends Foo{
}
For given use case Lombok will not be able to generate methods to set value of parameter defined in Foo class.
A workaround for this is:
Manual creating constructor of Bar.
Putting a Builder annotation on that constructor.
Is there a better workaround ?

Lombok has introduced experimental features with version: 1.18.2 for inheritance issues faced with Builder annotation, and can be resolved with #SuperBuilder annotation as below.
#SuperBuilder
public class ParentClass {
private final String a;
private final String b;
}
#SuperBuilder
public class ChildClass extends ParentClass{
private final String c;
}
Now, one can use Builder class as below (that was not possible with #Builder annotation)
ChildClass.builder().a("testA").b("testB").c("testC").build();

I leave this here for reference, as other answers demonstrate there is now (not at the time this answer was posted) a #SuperBuilder feature available now in the library which seems more fitting.
It´s a bit hidden, but people have had this question before, see:
https://reinhard.codes/2015/09/16/lomboks-builder-annotation-and-inheritance/
To summarise the blog post
#AllArgsConstructor
public class Parent {
private String a;
}
public class Child extends Parent {
private String b;
#Builder
private Child(String a, String b){
super(a);
this.b = b;
}
}
Would allow you to use
Child.builder().a("testA").b("testB").build()

There is a solution to this problem currently in the works. Check the progress here: https://github.com/rzwitserloot/lombok/pull/1337

Related

Slf4j logger log fields from parent class

I have following code:
#Slf4j
public class Main {
#Data
public static class AClass {
private String title;
}
#Data
public static class BClass extends AClass {
private Integer amount;
}
public static void main(String[] args) {
BClass bClass = new BClass();
bClass.setAmount(12);
bClass.setTitle("title");
log.info("{}", bClass);
}
}
In logs I can see only fields from BClass and not AClass :
17:52:28.151 [main] INFO Main - Main.BClass(amount=12)
But I want to see all BClass fields including ones that are in parent class(title field). How can I do it?
That's normal - what you're looking at (specifically, the Main.BClass(amount=12) part in your log) is the output of the toString() method of BClass. The #Data annotation is making a toString implementation for you. However, lombok intentionally does not include superclass information normally. You should be seeing a warning on your #Data annotation that explains this. One way to solve that problem is to add #ToString(callSuper = true) next to your #Data annotation.
This isn't about SLF4J or any logging framework - it's about what is and isn't in the generated toString() implementation.

Lombok Inherithance: how to set default value for field in superclass without redeclaring it?

I am using Lombok and I need a way to automatically set the value for a superclass field in the subclass, without redeclaring it.
It is working fine on its own (#SuperBuiler and #Builder.Default) when using the builder, but it is giving troubles with Spring-Data MongoDB.
org.springframework.data.mapping.MappingException: Ambiguous field mapping detected! Both protected test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.EvaluationModel.type and private test.jackson.polymorphism.domain.enumeration.EvaluationType test.jackson.polymorphism.domain.models.QuantitativeEvaluationModel.type map to the same field name type! Disambiguate using #Field annotation!
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.assertUniqueness(BasicMongoPersistentEntity.java:368)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:354)
at org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity$AssertFieldNameUniquenessHandler.doWithPersistentProperty(BasicMongoPersistentEntity.java:348)
These are my classes:
public enum EvaluationType{
QUALITATIVE, QUANTITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public abstract class EvaluationModel {
protected EvaluationType type;
protected Integer evaluation;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QualitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUALITATIVE;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
public class QuantitativeEvaluationModel extends EvaluationModel {
/**
* How to set the default value for this field
* without redeclaring it?
*/
#Builder.Default
private EvaluationType type = EvaluationType.QUANTITATIVE;
#Builder.Default
private String currencyCode = Currency.getInstance("EUR").getCurrencyCode();
private BigDecimal economicValue;
}
How do I make these subclass to have a default value (mayb final) for the type field, but without redeclaring the field?
This should work both with the contructor and with the builder.
A solution to fix this problem (not specific to Lombok) is to use an initializer block (https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html) in the subclasses.
#Data
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
{
super.type = EvaluationType.QUALITATIVE;
}
}
From your description, it sounds a bit like that you don't want a settable field, but just a fixed value depending on the subclass instance. In that case, a manually implemented getter method without any field is the better choice.
However, if you really want the user to be able to set a value different from the default, this is your way:
#SuperBuilder
public class QualitativeEvaluationModel extends EvaluationModel {
public static abstract class QualitativeEvaluationModelBuilder<C extends QualitativeEvaluationModel, B extends QualitativeEvaluationModelBuilder<C, B>> extends EvaluationModel.EvaluationModelBuilder<C, B>
{
public QualitativeEvaluationModelBuilder() {
type(EvaluationType.QUALITATIVE);
}
}
}
It defines the builder class exactly like Lombok would create it (Lombok will add anything that you didn't write manually). As it's really filled with generics, I used delombok to get it right.
Next, you have to define a default value for your builder instance simply by calling the builder's setter method for type in the builder's constructor.
In this way, users that don't call the type() method on the builder instance will get the default value. If you want something else as type, just call the type() method on the builder instance.
Note that this only affects the builder. If you also want to instantiate via (no-args) constructor, you have to manually implement the constructor and set the default there.

Can I use the Lombok #Builder passing the parent class as parameter?

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();
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.
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.
I would suggest you use #SuperBuilder
#SuperBuilder was introduced as experimental feature in lombok v1.18.2.
The #SuperBuilder annotation produces complex builder APIs for your
classes. In contrast to #Builder, #SuperBuilder also works with fields
from superclasses. However, it only works for types. Most importantly,
it requires that all superclasses also have the #SuperBuilder
annotation.
#Getter
#SuperBuilder
public class Parent {
public String name;
public String value;
}
#Getter
#SuperBuilder
public class Child extends Parent {
public String subValue;
}
Then all you need to do is
Child.builder().name("a").value("b").subValue("c").build();

Google Guice and Lombok - #AllArgsConstructor(onConstructor = #__(#Inject)) for abstract class

I find #AllArgsConstructor(onConstructor = #__(#Inject)) is helpful to keep code clean when working with Google Guice. I can save the constructor code. For example:
#AllArgsConstructor(onConstructor = #__(#Inject))
public class SomeClass {
private final DependentClassOne classOne;
private final DependentClassTwo classTwo;
// ...
}
For abstract class, I'm able to use #Inject for constructor.
#AllArgsConstructor(onConstructor = #__(#Inject))
public abstract class AbstractParentClass {
private final DependentClassOne classOne;
}
public class ChildClass extends AbstractParentClass {
private final DependentClassTwo classTwo;
#Inject
public ChildClass(final DependentClassOne classOne, final DependentClassTwo classTwo) {
super(classOne);
this.classTwo = classTwo;
}
}
Is it possible to save the constructor code in ChildClass by using something like #AllArgsConstructor(onConstructor = #__(#Inject))?
No, it is not possible to define AllArgsConstructor in child class when there is parent constructor due to the limitation of Lombok (see this issue on GitHub and another answer on SO).
You could mix field/setter injection in parent with constructor injection in child, but I would advise to avoid it.

Does lombok #Builder allow extends

I have 2 classes:
import lombok.Builder;
#Builder
public class B extends A {
}
and
import lombok.Builder;
#Builder
public class A {
}
on the #Builder on B I get the message:
The return type is incompatible with A.builder().
Is this a limitation of lombok? or something I'm doing wrong?
If I leave the #Builder off A, then the Builder on B doesn't seem to consider the fields in A in the constructors for B.
The latest lombok release 1.18.2 includes the new experimental #SuperBuilder. It supports inheritance and fields from superclasses (also abstract ones). The only requirement is that all superclasses must have the #SuperBuilder annotation. With it, the solution is as simple as this:
#SuperBuilder
public class B extends A {
private String b;
}
#SuperBuilder
public class A {
private String a;
}
B instance = B.builder().b("b").a("a").build();
It is only possible with a workaround (See #78)
From Reinhard.codes
We have been using #Builder on the class itself, but you can also put it on a class’s constructor or on a static method. In that case, Lombok will create a setter method on the builder class for every parameter of the constructor/method. That means you can create a custom constructor with parameters for all the fields of the class including its superclass.
#AllArgsConstructor
public class Parent {
private String a;
}
public class Child extends Parent {
private String b;
#Builder
private Child(String a, String b){
super(a);
this.b = b;
}
}
faced issue using lombok with java inheritance, resolved after using the below annotations on parent and child class:
#EqualsAndHashCode(callSuper = true)
#SuperBuilder
#Data
#AllArgsConstructor
#NoArgsConstructor
I cannot reproduce your exact problem anymore, but this may be because Lombok has evolved.
Part of your question, however, was that the builder for be does not include the fields for a. That remains true, as is also for #AllArgsConstructor. Inheritance is not Lombok’s strong suit.
Thad said, since you can write your constructor yourself and can put #Builder on the constructor, the following will generate a builder for B just as you wished:
#Builder
public class A {
String a;
}
public class B extends A {
#Builder
B(String a, String b) {
super(a);
this.b = b;
}
String b;
}
Without knowing the implementation details of lombok or trying it out i'd say no because the pattern won't allow it.
If you implement the builder pattern all of your methods (except of build() ) will always have the class which the builder exists for as return type.
That means class A's methods will only return A. So does B always return B.
If you now let B extend from A it will not override A's methods because it's return type does not match. Vice versa it cannot implement the builder methods in B because those methods already exist in A. They cannot coexist by OOP design.
You may be able to create a generic builder but that does not solve the problem. If you really need to extend from A you're problem may come from another design decision which the builder pattern cannot solve.
I'd assume that instead of extending the class you'd have default values in your builder which lombok should support. Those default values then reflect what class A may support by default. In a use-case where you'd rather have B doing stuff you'd then call the builder methods and override those default values.
Edit: Oh and maybe have a look here

Categories

Resources