I've recently seen some code (amazon hadoop code) that utilizes this type of syntax
Foo bar = new Foo().setX(10).setY(11);
I thought that was sweet so I decided to give it a go. made my setX()
type functions return Foo
instead of void
and put return this;
in all of them. this worked well. until I tried it with inheritance, which produced some strage results.
I'll give a concrete example: I have two classes, Location
class that has two fields, x and y. and another class Location3D
that inherits from Location
and adds a third field, z.
all the fields use the method described above for their setters.
now I want to create a new location3D instance and set its fields, what happens is
new Location3D().setZ(7).setY(6).setX(5)
works while
new Location3D().setX(7).setY(6).setZ(5)
doesn't.
by doesn't work I mean that what returns from setY(6)
is a Location
object and not a location3D
object and therefore does not have a setZ()
method!
after this long intro my question is: can this form of "setter stringing" be made to work with inheritance without forcing the caller to cast objects ? if so how ?
also I'm sure there's a term for this better then "setter stringing", what is it ?
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;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With