In this post I will show how something as non-intuitive (read ugly) as recursive generics can help to solve problem of creating extendable Builders.
But first we need to define what are we trying to achieve. We need to build set of Builder objects that facilitate creation of non-trivial domain objects and provide fluent interface (for the definition of fluent interface see Martin Fowler’s article) for user along the lines.
So let’s start with defining our 3 builder classes:
- BaseBuilder
- ConstraintBuilder
- And finally ConcreteBuilder
Now we want to write client code that uses ConcreteBuilder
. The code we want to have is the following:
Notice however that this code has compilation error:
"The method equalTo(int) is undefined for the type BaseBuilder"
.
Java compiler tells us that we are trying to invoke method on the class BaseBuilder
whereas we are assuming to be working with class ConcreteBuilder
.
OK, so how do we suppose to fix the code. I know of 2 possible solutions:
- Use covariant return types and override methods from base classes
- Use recursive generics when defining builder classes
Covariant return types
In object-oriented programming, a covariant return type of a method is one that can be replaced by a “narrower” type when the method is overridden in a subclass.
Thus knowing this we can fix our builders by overriding methods from parent classes and forcing them to return instance of the leaf class instead. So here is how our classes end up looking in the end:
- BaseBuilder (not changed)
- ConstraintBuilder
- ConcreteBuilder
With these changes in place our client code now compiles and runs, but we had to do too much work in my opinion. Notice for instance that we had to override all methods from base classes in each and every derived class.
We are forced to do this whenever subclass adds new methods, cause we need to provide end user with flexibility of invoking methods in any order irrespective of where they are defined.
Also in this example it was not necessary to do this for ConcreteBuilder
class but I showed it here for completeness, mainly to stress how much involved first approach is.
Enough for the ugliness, so let’s checkout second approach.
Recursive generics
OK, so is there better way to achieve the same goal without repeating same steps for each builder subclass? The answer is yes.
We can use generics while defining our builders to tell Java that return type of methods is not the builder’s class but rather the subclass of the builder, hence recursive generic definition.
Let’s see what it means for our builders:
- BaseBuilder
- ConstraintBuilder
- ConcreteBuilder
And the client code haven’t changed at all, it still compiles and runs as before:
Let’s have a quick recap of what we just did. We changed definition of the BaseBuilder
class to contain recursive generic parameter E (i.e. BaseBuilder<E extends BaseBuilder>
). This allowed us change return type of the methods from BaseBuilder
to E
, which effectively tells Java compiler that method returns some subclass of BaseBuilder
class.
Then we did the same for ConstraintBuilder
class (i.e. ConstraintBuilder<E extends ConstraintBuilder>
) which again tells javac that return type is some subclass of ConstraintBuilder
class.
And finally we stopped recursion for class ConcreteBuilder
by specifying itself as the generic parameter while extending ConstraintBuilder
class (i.e. ConcreteBuilder extends ConstraintBuilder<ConcreteBuilder>
). Since this class is at the bottom of our inheritance hierarchy we could stop using generic parameter. However if it would have subclasses on it’s own then we would have to repeat declaration similar to that of ConstraintBuilder
class.
Notice how much less effort was to implement second solution. Despite of some minor noise such as usage of @SuppressWarnings("unchecked")
annotation to eliminate compilation warnings and casts to E
this solution is much cleaner, easier to understand and maintain. There is no code duplication that we saw in first solution and it scales well when the inheritance hierarchy is getting bigger and deeper.
To conclude:
Java’s generics can be real pain (especially when wildcards are used), but in some cases clever usage of generics can save your day! 😉
August 15th, 2012 at 14:24
Thank you for this post Dmitry, needed it today 🙂
August 16th, 2012 at 09:02
You are welcome! 😉
April 16th, 2014 at 20:05
Hey Dmitri, what to do if I have one more level of class hierarchy and I want to build both the leaf class and its parent class ?
In that case, I can’t make the leaf class’ parent class as abstract.
I might have to mix-n-match generics up to 2 level and then use covariant return types for the 3rd level leaf node ?
Any better solution than this ?
Thanks.
October 13th, 2014 at 23:21
In a most strict approach, all classes should be either “final” or “abstract,” and thus you’re hierarchy of A <- B <- C would include a fourth class A <- B <-D where C and D are the classes you want to instantiate, and they share the common abstract base class B. It is a bit out of the way, but it is more maintainable in the long run.
October 13th, 2014 at 23:18
Get rid of the @suppressWarnings(“unchecked”) and unchecked casts to “E” by adding a method:
protected abstract E self();
to BaseBuilder and have the derived class implement it as follows:
protected ConstraintBuilder self() { return this; }
May 20th, 2015 at 21:03
Great hint, Alex! Instead of using “return (E) this” for the return value from methods one can simply use “return self()”.
March 11th, 2018 at 12:05
Useful post, Thank you 🙂
February 11th, 2019 at 12:37
Wouldn’t it be the same if it was defined as
BaseBuilder
instead of
BaseBuilder<E extends BaseBuilder> ?
What does the recursive definition bring?
February 11th, 2019 at 12:45
Wow, that code got butchered. I was trying to say if:
BaseBuilder⟨E extends BaseBuilder“⟩
was used instead of
BaseBuilder⟨E extends BaseBuilder⟨E⟩⟩
Wouldn’t the effect be the same?