Thursday, August 15, 2013

Return Type Covariance, The Forgotten Form of Polymorphism

Hey, that's a real catchy title, isn't it? This post is inspired by a programmer who I know and respect who always likes to ask in interviews, "What is polymorphism?" Good question, conceptually simple, but full of the kinds of details that can really separate thems that know sumpin' from thems that don't. (Sorry, I think living in the South has gotten to me.)

First, the basic definition: polymorphism is the idea that there's more than one way to satisfy a contract. Say I have a class called Animal, another called Dog and a third called Cat. Dog and Cat both extend Animal, to use the Java terminology. Without going into the details, anywhere any code expects an Animal, you can supply a Dog, a Cat, or any other object that extends Animal. We say that Dog and Cat are substitutable for Animal because anywhere you need an Animal you can substitute a Dog or a Cat. There's a lot more to be said about that, and maybe I will at some point, but for now I'm moving on to return type covariance.

If Dog and Cat are both derived from Animal, then they could be said colloquially to be more specific than Animal. The set of all Dogs is a subset of Animals. Dog and Cat are partitions of Animal. Dog and Cat are subtypes of Animal, and Animal is a supertype of Dog and Cat. Computer scientists would say that Dog and Cat are both narrower than Animal.
All of those statements express the same concept.

Here's some code for reference:

public class Animal
{
   public String speak()
   {
      return "Basic animal noise";
   }
}

public class Dog extends Animal
{
   @Override
   public String speak()
   {
      return "Woof";
   }
}

public class Cat extends Animal
{
   @Override
   public String speak()
   {
      return "Meow";
   }
}

Do you see that there's a method named speak() defined in Animal that's redefined in both Cat and Dog? And the @Override annotation that precedes those redefinitions? Yeah, yeah, standard textbook polymorphism. But have you ever done this?

public class Animal
{
...
   public Animal getSelf()
   {
      return this;
   }
}

public class Dog
{
...
   @Override
   public Dog getSelf()
   {
      return this;
   }
}

public class Cat
{
...
   @Override
   public Cat getSelf()
   {
      return this;
   }
}

Even though Animal defines the return type of getSelf() as Animal, Dog and Cat have successfully redefined that (as evidenced by the @Override annotation) to return instances of their own narrower type. That's legal Java, and it's pretty useful at times. They have redefined a method using a return type that's narrower than that returned by the method declaration that they're overriding. Catch that? Narrower return type? That's what "return type covariance" is.

Why is that ok? It's because the original method definition promised an Animal, and that's exactly what you're getting from all three. Dog is an Animal. Cat is an Animal. So it's ok if they declare the fact that you can expect a narrower form of Animal, right?

So the next time someone asks you what polymorphism is, don't just spout off the usual pablum about redefining methods in subtypes and such. Mention return type covariance. They'll be impressed.

Happy coding!