5.12 Polymorphism
A supertype reference can denote an object of its subtypes—conversely, a subtype object can act as an object of its supertypes. This is because there is an is-a relationship between a subtype and its supertypes.
Since a supertype reference can denote objects of different types at different times during execution, a supertype reference is said to exhibit polymorphic behavior (meaning has many forms). A subtype object also exhibits polymorphic behavior, since it can act as any object of its supertypes.
When a non-private instance method is called, the method definition executed is determined by dynamic method lookup, based on the type of the object denoted by the reference at runtime. Dynamic method lookup (also known as late binding, dynamic binding, and virtual method invocation) is the process of determining which method definition a method call signature denotes at runtime. It is performed starting in the class of the object on which the method is invoked, and moves up the inheritance hierarchy to find the supertype with the appropriate method definition.
For a call to an overridden method using a supertype reference, dynamic method lookup can determine a different method definition to execute at different times depending on the type of the object that is denoted by the supertype reference during execution. The overridden method is said to exhibit polymorphic behavior. Exploiting this polymorphic behavior is illustrated next.
The inheritance hierarchy depicted in Figure 5.7 is implemented in Example 5.23. Note that both the draw() and area() methods are overridden in Figure 5.7. The abstract method draw() in the IDrawable interface at (1) is implemented in all subclasses of the abstract class Shape. It is also implemented by the class Graph. The invocation of the draw() method in the three loops at (3), (4), and (6) in Example 5.23 relies on the polymorphic behavior of references and dynamic method lookup. The array drawables holds IDrawable references that can be assigned the reference value of an object whose class implements the IDrawable interface: a Square, a Circle, a Rectangle, and a Graph, as shown at (2). At runtime, dynamic lookup determines the draw() method implementation that will execute, based on the type of the object denoted by each element in the array.
Figure 5.7 Type Hierarchy to Illustrate Polymorphism
Example 5.23 Using Polymorphism
// File: PolymorphRefs.java
interface IDrawable { // (1)
void draw();
}
//_______________________________________________________________________________
abstract class Shape implements IDrawable {
abstract public void area();
}
//_______________________________________________________________________________
class Circle extends Shape {
@Override public void draw() { System.out.println(“Drawing a Circle.”); }
@Override public void area() {
System.out.println(“Computing area of a Circle.”);
}
}
//_______________________________________________________________________________
class Rectangle extends Shape {
@Override public void draw() { System.out.println(“Drawing a Rectangle.”); }
@Override public void area() {
System.out.println(“Computing area of a Rectangle.”);
}
}
//_______________________________________________________________________________
class Square extends Rectangle {
@Override public void draw() { System.out.println(“Drawing a Square.”); }
@Override public void area() {
System.out.println(“Computing area of a Square.”);
}
}
//_______________________________________________________________________________
class Graph implements IDrawable {
@Override public void draw() { System.out.println(“Drawing a Graph.”); }
}
//_______________________________________________________________________________
public class PolymorphRefs {
public static void main(String[] args) {
IDrawable[] drawables
= {new Square(), new Circle(), new Rectangle(), new Graph()}; // (2)
System.out.println(“Draw drawables:”);
for (IDrawable drawable : drawables) { // (3)
drawable.draw();
}
System.out.println(“Only draw shapes:”);
for (IDrawable drawable : drawables) { // (4)
if (drawable instanceof Shape) { // (5)
drawable.draw();
}
}
System.out.println(“Only compute area of shapes:”);
for (IDrawable drawable : drawables) { // (6)
if (drawable instanceof Shape shape) { // (7)
shape.area(); // (8)
}
}
}
}
Output from the program:
Draw drawables:
Drawing a Square.
Drawing a Circle.
Drawing a Rectangle.
Drawing a Graph.
Only draw shapes:
Drawing a Square.
Drawing a Circle.
Drawing a Rectangle.
Only compute area of shapes:
Computing area of a Square.
Computing area of a Circle.
Computing area of a Rectangle.
Leave a Reply