Howdy -- thought I'd add my bits...
One way to think about polymorphism is that anytime the reference variable type and the actual object type are different, polymorphism is happening! : )
So, to look at how polymorphism is used in the Java API (and in your own code), look at the reference variable type for the argument and/or return type of a method you're calling.
For example, if you put something in, say, an ArrayList (or any other collection), the ArrayList can't possibly have add() methods overloaded to take anything you could ever create to send it, so the ArrayList add() method looks like;
add(Object o)
Type 'Object' is the *ultimate* polymorphic type, because *any* type of object can be at the other end of an Object reference type...
Object obj = new Dog();
Object obj2 = new JButton();
etc.
So if Object is used as an argument type:
public void takeObject(Object o) {
}
ANYTHING can be passed to that method!
Dog d = new Dog();
takeObject(d);
JButton button = new JButton("click");
takeObject(button);
And so on...
So all of the collection classes (HashMap, LinkedList, ArrayList, etc.) all have methods that can take an Object, so that you can pass ANYTHING into those methods.
But the *real* object type is from whatever class *you* instantiated (Dog, JButton, etc.).
This lets you make incredibly flexible code. For example:
Imagine you're building the PetShop program. It's a simulation that will have dogs, cats, and birds. You're in charge of writing the PetShop program. So you write it like this;
class PetShop {
Dog[] dogArray;
Cat[] catArray;
Bird[] birdArray;
// methods that take a Dog, Cat, Bird and add them
// to the correct Array
void addDog(Dog d) {...}
void addCat(Cat c) { ... }
void addBird(Bird b) { ...}
// more methods here
}
class Dog {
void eat() {
// dog-specific eat behavior
}
void showHappiness() { }
}
class Cat {
void eatFood() {
// cat-specific eat behavior
}
}
class Bird {
void doEat() {
// bird-specific eat behavior
}
void beFriendly() { }
}
And imagine now it's time to call the eat() method on each of the animals in the PetShop. So you loop through each of the three arrays, and tell each Animal to eat.
for (int i = 0; i < dogArray.length; i++) {
dogArray[i].eat();
}
for (int i = 0; i < catArray.length; i++) {
catArray[i].eatFood();
}
for (int i = 0; i < birdArray.length; i++) {
birdArray[i].doEat();
}
And when it's time to be friendly or happy, you do the same thing, looping through each of the arrays and calling whatever the appropriate method is for that animal type (beFriendly(), showHappiness(), etc.)
Now you're done, and you leave to go on vacation.
But as soon as you leave, the spec changes (because as we all know, the spec ALWAYS changes
)
NOW the spec calls for not just Dogs, Cats, and Birds, but also Turtles, Fish, and Rabbits.
You're stuck!
Your boss calls your cellphone and tells you to catch the next plane to get back and change it. You have to go back in and rewrite your entire PetShop class to add new methods and arrays, and you have to find out what the eat method signature is in each of those new animal classes.
But this time, you're smart (and desperate to get back to that tropical island). So you do the following to the PetShop program:
1) Rewrite all the Animal classes to extend Animal:
abstract class Animal {
public void eat();
public void beFriendly();
}
You make it abstract, because you want this class ONLY for the purpose of polymorphism, and you do not want anyone to instantiate an Animal, because what would it look like? What would it eat like? Too creepy to think about. There is no such concrete thing as an Animal. Only subtypes of Animal (Dog, Cat, etc.)
You give the Animal class an eat() method, so that ALL Animal subtypes (subclasses) will have the same eat() method, and so that eat() can be called polymorphically (you'll see in a minute).
Now you make all animal classes extend Animal, and you override the two methods, giving them behavior specific for that particular Animal type:
class Dog extends Animal {
public void eat() {...}
public void beFriendly() {...}
}
class Cat extends Animal {
public void eat() {...}
public void beFriendly() {...}
}
class Bird extends Animal {
public void eat() {...}
public void beFriendly() {...}
}
class Turtle extends Animal {
public void eat() {...}
public void beFriendly() {...}
}
and so on for each Animal type...
NOW your PetShop methods can be simplified!!
class PetShop {
Animal[] animals; // instead of one per type
public void add(Animal a) {
// instead of one add method for each kind,
// just have ONE method that can take ANY
// Animal subclass type
}
public void makeThemEat() {
// instead of looping through EACH array,
// loop through just the Animal array and call
// eat() on WHATEVER animal happens to
// be at that index in the array
for(int i = 0; i < animals.length; i++) {
animals[i].eat();
}
}
public void beHappy() {
// instead of looping through EACH array,
// loop through just the Animal array and call
// beFriendly() on WHATEVER animal happens to
// be at that index in the array
for(int i = 0; i < animals.length; i++) {
animals[i].beFriendly();
}
}
So now ALL your code is much simpler, and NOW you can go on vacation and when somebody else wants to come along and add new Animal types to the PetShop program, class PetShop doesn't have to change!! It can take ANY kind of Animal, as long as that new Animal extends class Animal. And that's what you tell your co-workers as you're heading out the door wearing your Hawaiin shirt: "If you want to add new animals to the PetShop program, just be sure that you make them subclasses of class Animal. That way, the class is guaranteed to have an eat() method and a beFriendly() method, and all methods in PetShop will be able to handle your new Animal type."
So everybody is happy.
Well, almost...
One day somebody comes along and wants to reuse your Animal class for a Zoo program. You think "no problem, OO is all about reuse." But then somebody points out that Tigers *do not*, *should not*, have a beFriendly() method. It appears that beFriendly should not be in class Animal, but should instead be in the lower-level subclasses. But if you do that, then you don't get to use polymorphism where you loop through an array of the superclass type, Animal, and call beFriendly() on each object.
What to do?
Make Pet an interface! That way, not all Animal types have to have a beFriendly() method.
class Animal {
void eat();
}
interface Pet {
void beFriendly();
}
class Dog extends Animal implements Pet {...}
class Cat extends Animal implements Pet { ...}
class Hippo extends Animal { }
// does NOT implement Pet!
And so on...
And here's the coolest part about interfaces -- you can have a class from some OTHER inheritance tree still be a Pet! So if someone comes along and adds virtual, computerized Pets which are clearly NOT actual animals, then they do not have to be from the same class hierarchy as the other animals, but can STILL be used in a PetShop program, if the PetShop takes Pet types instead of Animal types:
class RoboPet implements Pet {... }
// does NOT extend Animal
class PetShop {
// takes only things which are Pets
Pet[] pets;
void takePet (Pet p ) {...}
...
}
And:
class Zoo {
Animal [] animals; // may or may not be pets
void takeAnimal(Animal a) {...}
void doEat() {
// loop through and call eat()
}
}
So now you have reusable Animal classes, which do NOT assume that all Animal types can be pets and have a beFriendly() behavior. And you have a Pet interface so that if you want to use some Animal classes as Pets, you simply have those Animal subclasses also implement Pet. And if you want to have non-Animal types that can do Pet things, then have those non-Animal classes implement Pet, even though they do not extend Animal.
Flexibility, and the ability to take vacations without worrying about people reusing or extending your code, is one big key to polymorphism.
The more you design with polymorphism, the more flexible and powerful your code is.
The more you use interfaces and supertypes as arguments and return values (rather than more specific concrete subclass types), the more polymorphism you are using.
The other key to polymorphism is that in Java, ALL instance methods are virtual. In other words, if you invoke eat() on a Dog object, even though the reference type is Animal:
Animal a = new Dog();
a.eat();
It is ALWAYS the Dog's eat() method that runs.
The compiler cares deeply about the REFERENCE type. It checks the type of 'a', to see if it has an eat() method. Since eat() is defined in class Animal, no problem.
But at runtime, the VM cares deeply about OBJECT type. It looks on the heap to see what type of object is actually out there on the heap at the other end of the Animal reference. It finds a Dog, so it invokes the Dog's eat() method rather than the one in Animal.
Polymorphism is just a Beautiful Thing.
cheers,
-Kathy
p.s. sorry for such a long post, but I don't know how to talk about the Whole Big Thing without using examples, and I just get so excited about polymorphism that I go a little crazy