Covariant returns and method chaining in Java

As of version 1.5, or "5" as the marketing people have it, Java has been equipped with covariant method return values. Not a particularly obvious name, is it? What this means is that if a method of class Base returns an instance of class RetBase, then a derived class of Base (let's call it Derived) can implement a method with the same signature which returns a derived class of RetBase, rather than a RetBase itself (let's call it RetDerived).

All very well, but does this solve any problems?

Well, undoubtedly there are cases where a derived class would like to be able to specialise the return type of a method specified in its superclass/interface. One of these is the "self return", which I'm particularly fond of. This is essentially a trick to allow several lightweight method calls to be conveniently chained into one statement, which can be nice when all you're doing is using a bunch of property setters and it's arguably more readable to have them all on one line for once. Here's a demo:

public class MyClass {
    private int _a;
    private String _b;

    public MyClass setFoo(int a) { _a = a; return this; }

    public MyClass setBar(String b) { _b = b; return this; }

    void main(String[] args) {
        MyClass baz = new MyClass();
        baz.setFoo(42).setBar("Special number");
    }
}

You get the idea. I feel like I'm missing out on something if I return void from set methods!

There is an issue with the self-return in an inheritance setting, though, which is this: if I call a method of the base class in a method chain, the returned object will be a reference to the base class, and the character of the chain will change. Once a base class method has been called in the chain, only methods declared in the base class can be called afterwards.

public class MyBase {
    private int _a;
    public MyBase setFoo(int a) { _a = a; return this;}
}

public class MyDerived extends MyBase {
    private String _b;
    public MyDerived setBar(String b) { _b = b; return this; }

    void main(String[] args) {
          MyDerived baz = new MyDerived();
          baz.setFoo(42).setBar("Special number"); //< Error: setFoo() returns a MyBase
    }
}

This can be pretty surprising, when you forget to leave calls to base class methods to the end of the chain, so if you're serious about chaining you'd better make use of the covariant return and trivially override base class methods in each derived class. For example, in MyDerived:

public MyDerived setFoo(int a) { return super.setFoo(a); }

Of course, you could always stop chaining methods, but that wouldn't be half as much fun, would it?

Comments

Comments powered by Disqus