0

I'd like to solve such problem. I have some abstract class and a concrete class with setters that return the instance of that class:

@MappedSuperclass public abstract class BaseEntity implements Serializable { private Integer id; public Integer getId() { return id; } public BaseEntity setId(Integer id) { this.id = id; return this; } } 

next abstract:

@MappedSuperclass public abstract class NamedEntity extends BaseEntity { private String name; public String getName() { return name; } public NamedEntity setName(String name) { this.name = name; return this; } } 

and finally a concrete class:

@Entity public class Person extends NamedEntity { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } 

I'd like to use this kind of builder but in current setup it's not working due to different return types of parent setters

 public Person build() { Person person = new Person() .setId(1); //return BaseEntity instead of Person .setName("name") //returns NamedEntity instead of Person .setAddress("foo"); //return Person! return person; } 

of course ther's a workaround with overriden setters but.... can it be done other way using generics?

 @Override public Person setId(Integer id) { super.setId(id); return this; } @Override public Person setName(String name) { super.setName(name); return this; } 
1
  • Where is your build class located? Is it a member of BaseEntity? Builders don't have to be fluent. It looks like you're just constructing a Person then setting its attributes. What's wrong with person.setId(1);, person.setName("name");, etc.? Commented Oct 4, 2022 at 21:23

3 Answers 3

2

Thanks for all the sugestions I know the builder pattern, but in this particular case is the same workaround as overriding the methods setId and setName The point here is: it is possible that setId method will return the instance of child class the method is called from

let's say I'd like to put a complex object to my builder (why not?):

public class Person extends NamedEntity { private String address; ... getters/setters public Builder builder() { return new Builder(); } public final static class Builder { private final Person person; private Long id; private String name; private String address; private Builder() { this.person = new Person(); } public Builder withId(Long id) { person.setId(id); return this; } ..... other setters public Builder withDto(PersonDTO dto) { person .setId(dto.getId()) .setName(dto.getName()) .setAddress(dto.getAddress() } public Person build() { return person; } } } 

as you may guess the person.setId returns instance of BaseEntity

Sign up to request clarification or add additional context in comments.

Comments

1

You can use the same trick as enums (Enum), a generic type parameter for the child class.

@MappedSuperclass public abstract class BaseEntity<E extends BaseEntity<E>> implements Serializable { private Integer id; public Integer getId() { return id; } protected final E getThis() { return this; } public E setId(Integer id) { this.id = id; return getThis(); } } @MappedSuperclass public abstract class NamedEntity<E extends NamedEntity<E>> extends BaseEntity<E> { private String name; public String getName() { return name; } public E setName(String name) { this.name = name; return getThis(); } } 

For child classes of Person you need not continue with this pattern.

@Entity public class Person extends NamedEntity<Person> { private String address; public String getAddress() { return address; } public Person setAddress(String address) { this.address = address; return this; } } 

Now you can do_

Person einstein = new Person() .setId(76) .setName("Albert") .setAddress("Princeton, New Jersey"); 

The alternative is a Builder pattern, however it has the same inheritance problem, and you might end up with *.Builder classes inheriting from parent Builder classes.

I would even say it is not worth this boiler plate code, just for a fluent API (chained calls). The criteria API for instance does hardly need using created objects, and the passed values for the setters must come from some code too.

Also setters implies the classes are mutable. It would be much nicer if most fields were immutable. With entity classes unrealistic, but setters are an ugly initialisation. When possible use constructors/builders without setters.

3 Comments

This does not compile in Java SE 11. Have you tried it?
@newacct You are absolutely right. The result of the setters should have been BaseEntity<E>'' and ''NamedEntity<E> and even then Person probably will not work. I have no java compiler at hand. The OP has answered his own question - now I understand why.
One pattern that is used if you want to return E from those methods is you can define an abstract method getThis() in the base class that returns E, and then in the setter methods, you can return getThis();. Then each implementing class, where E is a specific type, would implement getThis() by returning this (or whatever instance of E they want; as long as it's an E, it's type safe).
1

You can implement the Builder pattern by introducing a nested class Builder with a set of self-returning methods (i.e. returning an instance of Builder) which can be chained in a fluent way.

Method Builder.build() should return an instance of Person.

Note that you setters of your entities can be void.

That's how implementation might look like:

public class Person extends NamedEntity { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public static class Builder { private Person person; public Builder() { this.person = new Person(); } public Builder name(String name) { person.setName(name); return this; } public Builder address(String address) { person.setAddress(address); return this; } public Builder id(Integer id) { person.setId(id); return this; } public Person build() { return person; } } } 

Usage example:

Person person = new Person.Builder() .name("Alice") .address("Wonderland") .id(1) .build(); 

Note:

  • There could be multiple ways to obtain an instance of Builder. You can introduce in the Person class a static method builder() returning a new Builder, or static methods like withName(String), withId(Integer) might also be handy (for inspiration have a look at User class from Spring Security).
  • When dialing with immutable objects, Builder class should have all the field of the target class duplicated instead of keeping the reference to the target object. And in such case, method build() would be responsible for constructing an instance of the target type.

1 Comment

hope this gets upvoted/accepted. it's a pity nowadays with new questioners...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.