Java method chaining and auto-cast
I stumbled into an interesting problem while playing around with java method chaining.
Basically, we have an object with a number of properties. We want to assign those properties in an inline manner.
Example:
MyType t = new MyType();
t.setA("a");
t.setB("b");
t.setC("c");
t.setD("d");
Doing the same through method chaining:
MyType t = new MyType().setA("a").setB("b").setC("c").setD("d");
This is possible because the types of setA(), setB(), … are the same as MyType.
This is how the implementation of MyType would look:
public class MyType {
private String a, b, c, d;
//constructor
public MyType() {
a = ""; b = ""; c = ""; d = "";
return this; //return back the current object
}
public MyType setA(String s) {
a = s;
return this;
}
public MyType setB(String s) {
b = s;
return this;
}
public MyType setC(String s) {
c = s;
return this;
}
public MyType setD(String s) {
d = s;
return this;
}
}
As you see, each method returns its own object at the end of the call so that the other methods can pick up and complete the chain.
Well, this works fine and dandy, but what about derived classes?
The wrong way to do it in java:
public class MyDerivedType extends MyType {
private String e;
//constructor
public MyDerivedType () {
super(); //We call the constructor of MyType
e = "";
}
public MyDerivedType setE(String s) {
e = s;
return this;
}
}
Use example:
MyDerivedType t = new MyDerivedType().setA("a")...
This throws an exception because it can’t find a method called setA(). That’s because the return type of setA remains MyType and it’s not MyDerivedType.
In order to address this problem, we may do this:
MyDerivedType t = (MyDerivedType)(new MyType().setA("a").setB("b"));
That is, we use our original type, then we cast the result to MyDerivedType.
That is a bad solution because we have to keep track of our supertypes at all times and that we get the ugliness of casts. The problem is compounded when we need to mix methods of the supertype with methods of derived types:
MyDerivedType t = ((MyDerivedType)(new MyType().setA("a").setB("b"))).setE("e");
What we have is an inability of java to cast types into their derived classes. That is, we cannot auto-cast from MyType to MyDerivedType:
MyDerivedType t = new MyType(); //throws an exception, incompatible types
So our solution is to manually override all the methods of the superclass into the derived classes:
public class MyDerivedType extends MyType {
private String e;
//constructor
public MyDerivedType () {
super(); //We call the constructor of MyType
e = "";
}
//Override notation to help readability, we can do without it, though
@Override
public MyDerivedType setA(String s) {
return (MyDerivedType)super.setA(s);
}
@Override
public MyDerivedType setB(String s) {
return (MyDerivedType)super.setB(s);
}
@Override
public MyDerivedType setC(String s) {
return (MyDerivedType)super.setC(s);
}
@Override
public MyDerivedType setD(String s) {
return (MyDerivedType)super.setD(s);
}
public MyDerivedType setE(String s) {
e = s;
return this;
}
}
With this, we can do:
MyDerivedType t = new MyDerivedType().setA("a").setB("b").setC("c").setD("d")
.setE("e");
It’s all nice and clean and this is how you should do it in Java.
However, there is still a small code duplication problem involved with all this overriding. It would be much easier if Java had auto-cast from derived types. Such a feature would not break type safety since we’re not casting from arbitrary types.
November 19, 2008 at 9:28 pm
Very interesting article, i bookmarked your blog
Best regards