I think you're talking about F-bounded types. I've found them very useful in situations where two hierarchies are directly correlated. The most clear case that comes to mind is the builder pattern, targeted to a hierarchy of classes. In this case, it could be of use to have a hierarchy of builders as well.
An example might shed some light. Consider the following (very stereotyped) hierarchy:
public abstract class Human {
protected String name;
protected int age;
}
public class Woman extends Human {
protected String loveMovie;
}
public class Man extends Human {
protected String soccerTeam;
}
Now, we want to create builders for Man and Woman. We could implement a builder for each one, duplicating the methods to set name and age attributes. However, as Man and Woman inherit from Human, we could have an abstract HumanBuilder, and make our WomanBuilder and ManBuilder inherit from it. This is where F-bounded types come in handy.
The Human class, along with its HumanBuilder, would be as follows:
public abstract class Human {
protected String name;
protected int age;
public static abstract class HumanBuilder<H extends Human,
T extends HumanBuilder<H, T>> {
protected String name;
protected int age;
@SuppressWarnings("unchecked")
public T name(String name) {
this.name = name;
return (T) this;
}
@SuppressWarnings("unchecked")
public T age(int age) {
this.age = age;
return (T) this;
}
protected void fill(H human) {
human.name = this.name;
human.age = this.age;
}
protected abstract H create();
public final H build() {
H human = this.create();
this.fill(human);
return human;
}
}
}
This would be the Woman class, along with its WomanBuilder:
public class Woman extends Human {
protected String loveMovie;
public static class WomanBuilder extends HumanBuilder<Woman, WomanBuilder> {
protected String loveMovie;
public WomanBuilder loveMovie(String loveMovie) {
this.loveMovie = loveMovie;
return this;
}
@Override
protected void fill(Woman woman) {
super.fill(woman);
woman.loveMovie = this.loveMovie;
}
@Override
protected Woman create() {
return new Woman();
}
}
}
And finally, here's the Man class, along with its ManBuilder:
public class Man extends Human {
protected String soccerTeam;
public static class ManBuilder extends HumanBuilder<Man, ManBuilder> {
protected String soccerTeam;
public ManBuilder soccerTeam(String soccerTeam) {
this.soccerTeam = soccerTeam;
return this;
}
@Override
protected void fill(Man man) {
super.fill(man);
man.soccerTeam = this.soccerTeam;
}
@Override
protected Man create() {
return new Man();
}
}
}
This approach saves quite a lot of lines of code, especially in a real-world use case.
As expected, using the builders does not require any casting:
Man man = new Man.ManBuilder()
.name("Joe")
.age(29)
.soccerTeam("Los Angeles Galaxy")
.build();
Woman woman = new Woman.WomanBuilder()
.name("Jane")
.age(25)
.loveMovie("Meet Joe Black")
.build();