Fluent setters with inheritance in java
As you pointed out, the reason that new Location3D().setX(7).setY(6).setZ(5)
doesn't work is because setX()
and setY()
return instances of Location
and not Location3D
.
You can get around this using generics (though the solution isn't particularly pretty) by adding a generic type parameter to your Location
class:
public class Location<T extends Location<T>> {
protected int x, y;
@SuppressWarnings("unchecked")
public T setX(int x) {
this.x = x;
return (T) this;
}
@SuppressWarnings("unchecked")
public T setY(int y) {
this.y = y;
return (T) this;
}
}
Your subclass Location3D
would then have set itself as the generic type parameter so that the superclass returns instances of Location3D
instead of Location
:
public class Location3D extends Location<Location3D> {
protected int z;
public Location3D setZ(int z) {
this.z = z;
return this;
}
}
Unfortunately, there's no way that I know of to avoid the warnings produced by the superclass, hence the @SuppressWarnings("unchecked")
annotations.
It's also worth noting that if you define your subclass such that the generic type parameter is a different class type, then you could wind up with ClassCastException
, hence you should document that restriction in your superclass for anyone who might want to create their own subclass.
Finally, chaining together method calls in the way you describe is usually referred to as method chaining. The style of setter methods that you're describing is closely related to the builder pattern.
The problem is that Location returns Location, as Location3D would return Location3D. To solve this, override the methods you want to use in Location3D and change the return type:
public class Location {
private int x;
private int y;
public Location setY(int y){
this.y = y;
return this;
}
public Location setX(int x){
this.x = x;
return this;
}
}
And Location3D:
public class Location3D extends Location {
private int z;
public Location3D setY(int y){
super.setY(y);
return this;
}
public Location3D setX(int x){
super.setX(x);
return this;
}
public Location3D setZ(int z){
this.z = z;
return this;
}
}
Composition approach:
public class Location {
private int x;
private int y;
public Location setY(int y){
this.y = y;
return this;
}
public Location setX(int x){
this.x = x;
return this;
}
}
And Location3D:
public class Location3D {
private Location location = new Location();
private int z;
public Location3D setY(int y){
location.setY(y);
return this;
}
public Location3D setX(int x){
location.setX(x);
return this;
}
public Location3D setZ(int z){
this.z = z;
return this;
}
}