11.15. Polymorphism

Polymorphism is a big word that you can break down into “poly” which means many and “morphism” which means form. So, it just means many forms. In Java it means that the method that gets called at run-time (when the code is run) depends on the type of the object at run-time.

This is simliar to a toddler toy that has pictures of animals and when a handle is pulled an arrow spins. When the arrow stops the toy plays the sound associated with that animal.

Picture of a See N Say toy

If you were simulating this toy in software you could create an Animal class that had an abstract makeNoise method. Each subclass of Animal would override the makeNoise method to make the correct noise for that type. This type of polymorphism is called inheritance-based polymorphism. You have a common parent class, but the behavior is specified in the child class.

Note

In Java an object variable has both a declared type or compile-time type and a run-time type or actual type. The declared type or compile-time type of a variable is the type that is used in the declaration. The run-time type or actual type is the class that actually creates the object.

The variable nameList declared below has a declared type of List and an actual or run-time type of ArrayList. The complier will check if the declared type has the methods or inherits the methods being used in the code and give an error if it doesn’t find the method(s). The List interface does have a add method so this code will compile. At run-time the execution environment will first look for the add method in the ArrayList class since that is the actual or run-time type. If it doesn’t find it there it will look in the parent class and keep looking up the inheritance tree till it finds the method. The method will be found, since otherwise the code would not have compiled.

List<String> nameList = new ArrayList<String>();
nameList.add("Hi");

The variable message declared below has a declared type of Object and an actual or run-time type of String. Since the declared type of message is Object thie code message.indexOf("h"); will cause a compiler error since the Object class does not have an indexOf method.

Object message = new String("hi");
message.indexOf("h");

Note

Any object variable can refer to an object of the declared type or any descendant (subclass) of the declared type at run-time. The class String inherits from the class Object so an Object variable can hold a reference to a String object. But, you can only call methods that are available in the Object class unless you cast it back to the String class.

At compile time the compiler uses the declared type to check that the methods you are trying to use are available to an object of that type. The code won’t compile if the methods don’t exist in that class or some parent class of that class. At run-time the actual method that is called depends on the actual type of the object. Remember that an object keeps a reference to the class that created it (an object of the class called Class). When a method is called at run-time the first place that is checked for that method is the class that created the object. If the method is found there it will be executed. If not, the parent of that class will be checked and so on until the method is found.

Check your understanding

    10-9-1: What is the output from running the main method in the Shape class?

    public class Shape {
       public void what() { System.out.print("Shape ");}
    
       public static void main(String[] args) {
    
          Shape[] shapes = {new Shape(), new Rectangle(), new Square(),
                            new Circle()};
          for (Shape s : shapes)
          {
             s.what();
             System.out.print(" ");
          }
       }
    
    }
    
    class Rectangle extends Shape {
       public void what() { System.out.print("Rectangle "); }
    }
    
    class Square extends Rectangle {
    }
    
    class Oval extends Shape {
       public void what() { System.out.print("Oval "); }
    }
    
    class Circle extends Oval {
       public void what() { System.out.print("Circle ");}
    }
    
  • Shape Shape Shape Shape
  • The Rectangle subclass of Shape overrides the what method so this can't be right.
  • Shape Rectangle Square Circle
  • The Square subclass doesn't not override the what method so it will use the one in Rectangle.
  • There will be a compile time error
  • This code will compile. The declared type can hold objects of that type or any subclass of the type.
  • Shape Rectangle Rectangle Circle
  • The Shape object will print Shape. The Rectangle object will print Rectangle. The Square object will also print Rectangle since it doesn't overrride the what method. The Circle object will print Circle.
  • Shape Rectangle Rectangle Oval
  • The Circle class does override the what method so this can't be right.

You can step through this code using the Java Visualizer by clicking on the following link Shape Example.

Check your understanding

    10-9-2: What is the output from running the main method in the GradStudent class?

    public class Student {
    
       public String getFood() {
          return "Pizza";
       }
    
       public String getInfo()  {
         return this.getFood();
       }
    
       public static void main(String[] args)
       {
         Student s1 = new GradStudent();
         s1.getInfo();
       }
    }
    
    class GradStudent extends Student {
    
      public String getFood() {
         return "Taco";
      }
    
    }
    
  • Pizza
  • This would be true if s1 was actually a Student, but it is a GradStudent. Remember that the run-time will look for the method first in the class that created the object.
  • Taco
  • Even though the getInfo method is in Student when getFood is called the run-time will look for that method first in the class that created this object which in this case is the GradStudent class.
  • You will get a compile time error
  • This code will compile. The student class does have a getInfo method.
  • You will get a run-time error
  • There is no problem at run-time.

You can step through this code using the Java Visualizer by clicking on the following link Student Example.

    10-9-3: What is the output from running the main method in the RaceCar class?

    public class Car
    {
      private int fuel;
    
      public Car() { fuel = 0; }
      public Car(int g) { fuel = g; }
    
      public void addFuel() { fuel++; }
      public void display() { System.out.print(fuel + " "); }
    
      public static void main(String[] args)
      {
         Car car = new Car(5);
         Car fastCar = new RaceCar(5);
         car.display();
         car.addFuel();
         car.display();
         fastCar.display();
         fastCar.addFuel();
         fastCar.display();
      }
    
    }
    
    class RaceCar extends Car
    {
      public RaceCar(int g) { super(2*g); }
    }
    
  • 5 6 10 11
  • The code compiles correctly, and because RaceCar extends the Car class, all the public object methods of Car can be used by RaceCar objects.
  • 5 6 5 6
  • RaceCar, while it inherits object methods from Car via inheritance, has a separate and different constructor that sets the initial fuel amount to 2 * g, thus in this case, fuel for fastCar is set to 10 initially.
  • 10 11 10 11
  • The variable car is a Car object, so the constructor used is not the same as the fastCar object which is a RaceCar. The car constructor does not change the passed in parameter, so it is set to 5 initially.
  • The code won't compile.
  • RaceCar inherits from the Car class so all the public object methods in Car can be accessed by any object of the RaceCar class.

You can step through the code using the Java Visualizer by clicking on the following link: Car Example.

    10-9-4: Given the following class definitions and a declaration of Book b = new Dictionary which of the following will cause a compile-time error?

    public class Book
    {
       public String getISBN()
       {
          // implementation not shown
       }
    
       // constructors, fields, and other methods not shown
    }
    
    public class Dictionary extends Book
    {
       public String getDefinition(String word)
       {
          // implementation not shown
       }
    }
    
  • b.getISBN();
  • The b object is actually a Dictionary object which inherits the getISBN method from Book.
  • b.getDefintion();
  • At compile time the declared type is Book and the Book class does not have or inherit a getDefintion method.
  • ((Dictionary) b).getDefinition();
  • Casting to Dictionary means that the compiler will check the Dictionary class for the getDefinition method.

    10-9-5: Assume that the following declaration appears in a client program Base b = new Derived();. What is the result of the call b.methodOne()?

    public class Base
    {
       public void methodOne()
       {
          System.out.print("A");
          methodTwo();
       }
    
       public void methodTwo()
       {
          System.out.print("B");
       }
    
       public static void main(String[] args)
       {
          Base b = new Derived();
          b.methodOne();
       }
    }
    
    class Derived extends Base
    {
       public void methodOne()
       {
          super.methodOne();
          System.out.print("C");
       }
    
       public void methodTwo()
       {
          super.methodTwo();
          System.out.print("D");
       }
    }
    
  • ABDC
  • Even though b is declared as type Base it is created as an object of the Derived class, so all methods to it will be resolved starting with the Derived class.
  • AB
  • This would be true if the object was created of type Base using new Base. But the object is really a Derived object. So all methods are looked for starting with the Derived class.
  • ABCD
  • After the call to methodOne in the super class printing "A", the code continues with the implicit this.methodTwo which resolves from the current object's class which is Derived. methodTwo in the Derived class is executed which then calls super.methodTwo which invokes printin "B" from methodTwo in the Base class. Then the "D" in the Derive methodTwo is printed. Finally the program returns to methodOne in the Derived class are prints "C".
  • ABC
  • The call to methodTwo in super.methodOne is to this.methodTwo which is the method from the Derived class. Consequently the "D" is also printed.

You can step through this code using the Java Visulaizer by clicking on the following link: Base Example.

Next Section - 11.16. Common Mistakes