Recently chain builder calls in Java have been gaining popularity, look, e.g. at
this.
The idea is that you have a builder that has small configuration methods and in the end builds the right stuff.
StringBuffer
works like this:
return new StringBuffer().append("Now is ").append(new Date()).append(", and ticking...").toString();
Or, more typically, something like this code:
new TestBuilder().limitTime(1000).forPackage(this.getClass().getPackage()).skipFlaky().build().run();
This way we, first, annotate our parameters, and second, bypass the boring law that demands writing each statement on a separate line.
And of course it makes the process more flexible.
Actually, the fact that it is a builder is unimportant. We can just chain calls if every method that in the previous life was returning void would start returning
this
.
But there's a problem here... what if our builder is not the ultimate version, but a subclass of a superbuilder? Look at an example below:
public class ChainCalls {
public static class A {
A do1(String s) {
System.out.println("A.1 " + s);
return this;
}
A do2(String s) {
System.out.println("A.2 " + s);
return this;
}
}
public static class B extends A {
B do0(String s) {
System.out.println("B.0 " + s);
return this;
}
B do1(String s) {
System.out.println("B.1 " + s);
return this;
}
B do3(String s) {
System.out.println("B.3 " + s);
return this;
}
}
public static void main(String[] args) {
((B)(new B().do0("zero").do1("one").do2("two"))).do3("three");
}
}
See how we have to cast the result of
do2()
? That's because the method of class
A
has no knowledge that it actually works in the context of class
B
.
How can we deal with this? One cheap solution is to duplicate all the methods in
B
and call the delegate, that is,
super.do1()
etc. Feasible, but not very nice, not very scalable, and not very Java5-ish.
Because we have generics, and we have a so-called covariant inheritance, so that - because we can!
An anonymous contributor in livejournal.com has suggested the following solution:
public class ChainCallsCovariant {
public static abstract class SuperA<T> {
abstract T self();
T do1(String s) {
System.out.println("A.1 " + s);
return self();
}
T do2(String s) {
System.out.println("A.2 " + s);
return self();
}
}
public static class A extends SuperA<A> {
A self() { return this; }
}
public static abstract class SuperB<T> extends SuperA<T> {
T do0(String s) {
System.out.println("B.0 " + s);
return self();
}
T do1(String s) {
System.out.println("B.1 " + s);
return self();
}
T do3(String s) {
System.out.println("B.3 " + s);
return self();
}
}
public static class B extends SuperB<B> {
B self() { return this; }
}
public static void main(String[] args) {
new B().do0("zero").do1("one").do2("two").do3("three");
}
}
The main trick here is to encapsulate "return this", and make it generic, so that we always, in the eyes of the compiler, return the instance of the right class.
P.S.
Here I've posted a better solution.