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!