How my Dog learned Polymorphism
So, Clover, let's review from our last lesson...
What do you get when you do this:
Dog d = new Dog();
You get a new Dog object right? An instance of class Dog, referenced by a variable named "d". [tail wags]
Good Girl! Here's a treat...
So let's go beyond that. First, I'm going to show you a class inheritance tree for the example we're going to work on:
This means:
Dog extends Animal
Animal is a superclass of Dog
Dog is a subclass of Animal
Dog inherits from Animal
Dog overrides the play() method
Dog adds one new method, bark()
Ruff!
Yes, very good.
So what happens now if I say:
Dog d = new Dog();
d.play();
[cocks head sideways]
Which play() method is called -- the one in Dog, or the one inherited from Animal?
Dog overrides the play() method, so you get the play() method in Dog.
Of course. That's what overriding means. The subclass object has "extended" the superclass by adding more specific functionality to the play() method.
OK, now what if I say...
Animal d = new Dog();
Is that code even legal? Will it compile?
Yes! Because Dog "is a" Animal. (or so you keep reminding me). Dog is a subclass of Animal, so I know that it's legal to have a reference type which is a superclass of the actual object type. Here, let me draw it [puts marker in mouth]
Wow! I had no idea you could draw. And yes, that's correct. The reference type and the object type don't always match, but the rule is:
If the reference type is a class, the object it refers to MUST be either that same type or a subclass of that type.
If the reference type is an INTERFACE, the object it refers to MUST be an object from a class which implements the interface (either directly or through inheritance).
I understand how it works, but doesn't it "trick" a programmer into thinking that he (or she) has an Animal object when really a Dog object is hiding out there on the heap?
But what's the danger in that? A Dog can do ANYTHING an Animal can do (and more). So there's no harm. A programmer can safely call methods on the Animal reference, as though the object were really an Animal. The object is guaranteed to be able to respond to those method calls.
But let's get to the Big Question:
Animal d = new Dog();
d.play();
Which play() is really called? The one in Animal, or the one in Dog?
Here's a hint: Java uses "late-binding" for instance methods.
Oh, well, that explains it...
Late-binding means Java doesn't "bind" (think: choose) a method call to an actual method at compile time. The choice of method happens later... at run-time.
Compile time = early, run-time = late. I get it.
Yes, exactly.
So Java waits to see what the ACTUAL object is... rather than using the reference type! Cool. [wags tail again]
You're really a smart dog, Clover. Despite what those sheep say.
And you're right.
Java does the right thing at run-time.
So if a Dog object is at the other end of an Animal reference, you still get the Dog
version of the method. Always. It's not like this in C++, where a method has to marked
"virtual" if you want to get this "polymorphic" behavior where the
method is looked-up at run-time, LATE.
Based on the actual object's class, not the reference type.
But I still have another question... WHY would you want to do this? If you wanted the Dog to play (is now OK?) why not just use a Dog reference? Why use the Animal reference?
Referring to an object in many different ways (reference type is different from actual object class) is the point of polymorphism, which means "many forms". I can treat a Dog object like a Dog, or I can treat it like an Animal, or I can even treat it like a Pet, when I have the Dog class implement the Pet interface.
...and the reason for this would be...
Many reasons. Here's a good one...
You have a program that uses many different animal subclasses, and tells all the animals
to play().
You could put all of the different animal subclass objects into an Animal [] array rather than keeping them in separate arrays for each subclass type.
Then you can loop through the Animal [] array and tell each element to play().
And the RIGHT play() method will be called for the REAL object at each slot in the array... am I right?
Yes! So the Compiler is...
.What, no treat? [doing that cute, sad, dog look]
Hang on and let me finish. So the Compiler is happy, since it knows that Animal has a play() method, it knows that each animal in the array can be safely treated like an Animal.
And if any element in the array happens to be a subclass of Animal, and it overrides the play() method, then a different play() method will be called for that element.
Late. The choice of the method is made "late".
At run-time.
So why not just use a generic collection like Vector? Can't you just put any object in there -- they don't even have to be from the same inheritance tree.
You've missed the point. A Vector would be less efficient, for one thing, and you'd have to cast the objects back to something more specific before you could call methods (other than, say, toString()).
What a pain. And that's not the main problem.
What if another programmer comes along after the guy who wrote the Animal program is gone? What if you don't even have his source code, but you need to add a new animal subclass like Cow? If all the different Animal subclasses had to be figured out, and cast, etc. then you could not add new Cow animals into the program without changing the original code.
But if the Animal program is simply expecting an array of Animals, then all I have to do is make my new Cow class a subclass of Animal, then it can be safely included in an Animal [] array and passed to a method in the Animal program.
So that's what you meant about extensibility with object-oriented programming...
Good girl! Yes -- that's part of what makes OOP so cool. OK, here's your treat. Now go write some code for me and I promise to keep the sheep away from your Duke chew toy.
[In Clover's next lesson, we'll be tackling AWT event-handling]