This is certainly possible with the recursive bound, but the subtype builders need to also be generic, and you need a few interim abstract classes. It’s a little bit cumbersome, but it’s still easier than the non-generic version.
/**
* Extend this for Mammal subtype builders.
*/
abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> {
String sex;
String name;
B sex(String sex) {
this.sex = sex;
return self();
}
B name(String name) {
this.name = name;
return self();
}
abstract Mammal build();
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
}
/**
* Use this to actually build new Mammal instances.
*/
final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> {
@Override
Mammal build() {
return new Mammal(this);
}
}
/**
* Extend this for Rabbit subtype builders, e.g. LopBuilder.
*/
abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>>
extends GenericMammalBuilder<B> {
Color furColor;
B furColor(Color furColor) {
this.furColor = furColor;
return self();
}
@Override
abstract Rabbit build();
}
/**
* Use this to actually build new Rabbit instances.
*/
final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> {
@Override
Rabbit build() {
return new Rabbit(this);
}
}
There’s a way to avoid having the “concrete” leaf classes, where if we had this:
class MammalBuilder<B extends MammalBuilder<B>> {
...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
extends MammalBuilder<B> {
...
}
Then you need to create new instances with a diamond, and use wildcards in the reference type:
static RabbitBuilder<?> builder() {
return new RabbitBuilder<>();
}
That works because the bound on the type variable ensures that all the methods of e.g. RabbitBuilder
have a return type with RabbitBuilder
, even when the type argument is just a wildcard.
I’m not much of a fan of that, though, because you need to use wildcards everywhere, and you can only create a new instance using the diamond or a raw type. I suppose you end up with a little awkwardness either way.
And by the way, about this:
@SuppressWarnings("unchecked")
final B self() {
return (B) this;
}
There’s a way to avoid that unchecked cast, which is to make the method abstract:
abstract B self();
And then override it in the leaf subclass:
@Override
RabbitBuilder self() { return this; }
The issue with doing it that way is that although it’s more type-safe, the subclass can return something other than this
. Basically, either way, the subclass has the opportunity to do something wrong, so I don’t really see much of a reason to prefer one of those approaches over the other.