Main Page

Previous Next

Polymorphism

Class inheritance is not just about reusing classes that you have already defined as a basis for defining a new class. It also adds enormous flexibility to the way in which you can program your applications, with a mechanism called polymorphism. So what is polymorphism?

The word 'polymorphism' generally means the ability to assume several different forms or shapes. In programming terms it means the ability of a single variable of a given type to be used to reference objects of different types, and to automatically call the method that is specific to the type of object the variable references. This enables a single method call to behave differently, depending on the type of the object to which the call applies.

Click To expand

There are a few requirements that need to be fulfilled to get polymorphic behavior, so let's step through them.

First of all, polymorphism works with derived class objects. It also depends on a new capability that is possible within a class hierarchy that we haven't met before. Up to now, we have always been using a variable of a given type to reference objects of the same type. Derived classes introduce some new flexibility in this. Of course, we can store a reference to a derived class object in a variable of the derived class type, but we can also store it in a variable of any direct or indirect base class type. More than that, a reference to a derived class object must be stored in a variable of a direct or indirect class type for polymorphism to work. For instance, as shown in the previous diagram, a variable of type Dog can be used to store a reference to an object of any type derived from Dog. If the Dog class was derived from the Animal class here, a variable of type Animal could also be used to reference Spaniel, Chihuahua, or Collie objects.

Polymorphism means that the actual type of the object involved in a method call determines which method is called, rather than the type of the variable being used to store the reference to the object. In the previous diagram, if aDog contains a reference to a Spaniel object, the bark() method for that object will be called. If it contains a reference to a Collie object, the bark() method in the Collie class will be called. To get polymorphic operation when calling a method, the method must be a member of the base class – the class type of the variable you are using – as well that of the class type of the object involved. So in our example, the Dog class must contain a bark() method, as must each of the derived classes. You cannot call a method for a derived class object using a variable of a base class type if the method is not a member of the base class. Any definition of the method in a derived class must have the same signature and the same return type as in the base class, and must have an access specifier that is no more restrictive. Indeed, if you define a method in a derived class with the same signature as a method in a base class, any attempt to specify a different return type, or a more restrictive access specifier, will be flagged as an error by the compiler.

The conditions that need to be met if you want to use polymorphism can be summarized as:

  • The method call for a derived class object must be through a variable of a base class type.

  • The method called must also be a member of the base class.

  • The method signature must be the same in the base and derived classes.

  • The method return type must be the same in the base and derived classes.

  • The method access specifier must be no more restrictive in the derived class than in the base.

When you call a method using a variable of a base class type, polymorphism results in the method that is called being selected based on the type of the object stored, not the type of the variable. Because a variable of a base type can store a reference to an object of any derived type, the kind of object stored will not be known until the program executes. Thus the choice of which method to execute has to be made dynamically when the program is running – it cannot be determined when the program is compiled. The bark() method that is called through the variable of type Dog in the earlier illustration, may do different things depending on what kind of object the variable references. As we will see, this introduces a whole new level of capability in programming using objects. It implies that your programs can adapt at runtime to accommodate and process different kinds of data quite automatically.

Note that polymorphism only applies to methods. It does not apply to data members. When you access a data member of a class object, the variable type always determines the class to which the data member belongs. This implies that a variable of type Dog can only be used to access data members of the Dog class. Even when it references an object of type Spaniel, for instance, you can only use it to access data members of the Dog part of a Spaniel object.

Using Polymorphism

As we have seen, polymorphism relies on the fact that you can assign an object of a subclass type to a variable that you have declared as being of a superclass type. Suppose you declare the variable:

Animal theAnimal;     // Declare a variable of type Animal 

You can quite happily make this refer to an object of any of the subclasses of the class Animal. For example, you could use it to reference an object of type Dog:

theAnimal = new Dog("Rover");

As you might expect, you could also initialize the variable theAnimal when you declare it:

Animal theAnimal = new Dog("Rover");

This principle applies quite generally. You can use a variable of a base class type to store a reference to an object of any class type that you have derived, directly or indirectly, from the base. We can see what magic can be wrought with this in practice by extending our previous example. We can add a new method to the class Dog that will display the sound a Dog makes. We can add a couple of new subclasses that represent some other kinds of animals.

Try It Out – Enhancing the Dog Class

First of all we will enhance the class Dog by adding a method to display the sound that a dog makes:

public class Dog extends Animal {
  // A barking method
  public void sound() {
    System.out.println("Woof    Woof");
  }

  // Rest of the class as before...
}

We can also derive a class Cat from the class Animal:

public class Cat extends Animal {
  public Cat(String aName) {
    super("Cat");         // Call the base constructor
    name = aName;         // Supplied name
    breed = "Unknown";    // Default breed value
  }

  public Cat(String aName, String aBreed) {
    super("Cat");         // Call the base constructor
    name = aName;         // Supplied name
    breed = aBreed;       // Supplied breed
  }

  // Return a String full of a cat's details
  public String toString() {
    return super.toString() + "\nIt's " + name + " the " + breed;
  }

  // A miaowing method
  public void sound() {
    System.out.println("Miiaooww");
  }

  private String name;     // Name of a cat
  private String breed;    // Cat breed
}

Just to make it a crowd, we can derive another class – of ducks:

public class Duck extends Animal {
  public Duck(String aName) {
    super("Duck");        // Call the base constructor
    name = aName;         // Supplied name
    breed = "Unknown";    // Default breed value
  }

  public Duck(String aName, String aBreed) {
    super("Duck");        // Call the base constructor
    name = aName;         // Supplied name
    breed = aBreed;       // Supplied breed
  }

  // Return a String full of a duck's details
  public String toString() {
    return super.toString() + "\nIt's " + name + " the " + breed;
  }

  // A quacking method
  public void sound() {
    System.out.println("Quack quackquack");
  }

  private String name;     // Duck name
  private String breed;    // Duck breed
}

You can fill the whole farmyard, if you need the practice, but three kinds of animal are sufficient to show you how polymorphism works.

We need to make one change to the class Animal. To select the method sound() dynamically for derived class objects, it needs to be a member of the base class. We can add a content-free version of sound() to the class Animal:

class Animal {
   // Rest of the class as before...

   // Dummy method to be implemented in the derived classes
   public void sound(){}
}

We need a program that will use these classes. To give the classes a workout, we can create an array of type Animal and populate its elements with different subclass objects. We can then select an object random from the array, so that there is no possibility that the type of the object selected is known ahead of time. Here's the code to do that:

import java.util.Random;

public class TryPolymorphism {
  public static void main(String[] args) {
    // Create an array of three different animals
    Animal[] theAnimals = {
                            new Dog("Rover", "Poodle"),
                            new Cat("Max", "Abyssinian"),
                            new Duck("Daffy","Aylesbury")
                          };

    Animal petChoice;                           // Choice of pet

    Random select = new Random();               // Random number generator
    // Make five random choices of pet
    for(int i = 0; i < 5; i++) {
      // Choose a random animal as a pet
      petChoice = theAnimals[select.nextInt(theAnimals.length)]; 

      System.out.println("\nYour choice:\n" + petChoice);
      petChoice.sound();                        // Get the pet's reaction
    }
  }
}

When I ran this I got the output:

Your choice:
This is a Duck
It's Daffy the Aylesbury
Quack quackquack
Your choice:
This is a Cat
It's Max the Abyssinian
Miiaooww

Your choice:
This is a Duck
It's Daffy the Aylesbury
Quack quackquack

Your choice:
This is a Duck
It's Daffy the Aylesbury
Quack quackquack

Your choice:
This is a Cat
It's Max the Abyssinian
Miiaooww

The chances are that you will get a different set from this, and a different set again when you re-run the example. The output from the example clearly shows that the methods are being selected at runtime, depending on which object happens to get stored in the variable petChoice.

How It Works

The definition of the sound()method in the Animal class has no statements in the body, so it will do nothing if it is executed. We will see a little later in this chapter how we can avoid including the empty definition for the method, but still get polymorphic behavior in the derived classes.

We need the import statement because we use a Random class object in our example to produce pseudo-random index values in the way we have seen before. The array theAnimals of type Animal contains a Dog object, a Cat object and a Duck object. We select objects randomly from this array in the for loop using the Random object, select, and store the selection in petChoice. We can then call the toString() and sound() methods using the object reference stored. The effect is that the appropriate method is selected automatically to suit the object stored, so our program operates differently depending on what type of object is referenced by petChoice.

Of course, we call the toString() method implicitly in the argument to println(). The compiler will insert a call to this method to produce a String representation of the petChoice object. The particular toString() method will automatically be selected to correspond with the type of object referenced by petChoice. This would still work even if we had not included the toString() method in the base class. We will see a little later in this chapter there is a toString() method in every class that you define, regardless of whether you define one or not.

Polymorphism is a fundamental part of object-oriented programming. We will be making extensive use of polymorphism in many of the examples we will develop later in the book, you will find that you will use it often in your own applications and applets. But this is not all there is to polymorphism in Java, and we will come back to it again later in this chapter.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor