10.3 Demonstrating Polymorphic Behavior
Section 9.4 created a class hierarchy, in which class BasePlusCommissionEmployee inherited from CommissionEmployee. The examples in that section manipulated CommissionEmployee and BasePlusCommissionEmployee objects by using references to them to invoke their methods—we aimed superclass variables at superclass objects and subclass variables at subclass objects. These assignments are natural and straightforward—superclass variables are intended to refer to superclass objects, and subclass variables are intended to refer to subclass objects. However, as you'll soon see, other assignments are possible.
In the next example, we aim a superclass reference at a subclass object. We then show how invoking a method on a subclass object via a superclass reference invokes the subclass functionality—the type of the referenced object, not the type of the variable, determines which method is called. This example demonstrates that an object of a subclass can be treated as an object of its superclass, enabling various interesting manipulations. A program can create an array of superclass variables that refer to objects of many subclass types. This is allowed because each subclass object is an object of its superclass. For instance, we can assign the reference of a BasePlusCommissionEmployee object to a superclass CommissionEmployee variable, because a BasePlusCommissionEmployee is a CommissionEmployee—we can treat a BasePlusCommissionEmployee as a CommissionEmployee.
As you'll learn later in the chapter, you cannot treat a superclass object as a subclass object, because a superclass object is not an object of any of its subclasses. For example, we cannot assign the reference of a CommissionEmployee object to a subclass BasePlusCommissionEmployee variable, because a CommissionEmployee is not a BasePlusCommissionEmployee—a CommissionEmployee does not have a baseSalary instance variable and does not have methods setBaseSalary and getBaseSalary. The is-a relationship applies only up the hierarchy from a subclass to its direct (and indirect) superclasses, and not vice versa (i.e., not down the hierarchy from a superclass to its subclasses).
The Java compiler does allow the assignment of a superclass reference to a subclass variable if we explicitly cast the superclass reference to the subclass type—a technique we discuss in Section 10.5. Why would we ever want to perform such an assignment? A superclass reference can be used to invoke only the methods declared in the superclass—attempting to invoke subclass-only methods through a superclass reference results in compilation errors. If a program needs to perform a subclass-specific operation on a subclass object referenced by a superclass variable, the program must first cast the superclass reference to a subclass reference through a technique known as downcasting. This enables the program to invoke subclass methods that are not in the superclass. We show a downcasting example in Section 10.5.
The example in Fig. 10.1 demonstrates three ways to use superclass and subclass variables to store references to superclass and subclass objects. The first two are straightforward—as in Section 9.4, we assign a superclass reference to a superclass variable, and a subclass reference to a subclass variable. Then we demonstrate the relationship between subclasses and superclasses (i.e., the is-a relationship) by assigning a subclass reference to a superclass variable. This program uses classes CommissionEmployee and BasePlusCommissionEmployee from Fig. 9.10 and Fig. 9.11, respectively.
Fig 10.1. Assigning superclass and subclass references to superclass and subclass variables.
1// Fig. 10.1: PolymorphismTest.java
2// Assigning superclass and subclass references to superclass and
3// subclass variables.
4 5 public class PolymorphismTest 6 { 7 public static void main( String[] args ) 8 { 9// assign superclass reference to superclass variable
10CommissionEmployee commissionEmployee = new CommissionEmployee(
11 "Sue", "Jones", "222-22-2222", 10000, .06 ); 12 13// assign subclass reference to subclass variable
14BasePlusCommissionEmployee basePlusCommissionEmployee =
15new BasePlusCommissionEmployee(
16"Bob"
,"Lewis"
,"333-33-3333"
,5000
,.04
,300
); 17 18// invoke toString on superclass object using superclass variable
19 System.out.printf("%s %s:\n\n%s\n\n"
, 20"Call CommissionEmployee's toString with superclass reference "
, 21"to superclass object"
,commissionEmployee.toString()
); 22 23// invoke toString on subclass object using subclass variable
24 System.out.printf("%s %s:\n\n%s\n\n"
, 25"Call BasePlusCommissionEmployee's toString with subclass"
, 26"reference to subclass object"
, 27basePlusCommissionEmployee.toString()
); 28 29// invoke toString on subclass object using superclass variable
30CommissionEmployee commissionEmployee2 =
31basePlusCommissionEmployee;
32 System.out.printf("%s %s:\n\n%s\n"
, 33"Call BasePlusCommissionEmployee's toString with superclass"
, 34"reference to subclass object"
,commissionEmployee2.toString() );
35 }// end main
36 }// end class PolymorphismTest
In Fig. 10.1, lines 10–11 create a CommissionEmployee object and assign its reference to a CommissionEmployee variable. Lines 14–16 create a BasePlusCommissionEmployee object and assign its reference to a BasePlusCommissionEmployee variable. These assignments are natural—for example, a CommissionEmployee variable's primary purpose is to hold a reference to a CommissionEmployee object. Lines 19–21 use commissionEmployee to invoke toString explicitly. Because commissionEmployee refers to a CommissionEmployee object, superclass CommissionEmployee's version of toString is called. Similarly, lines 24–27 use basePlusCommissionEmployee to invoke toString explicitly on the BasePlusCommissionEmployee object. This invokes subclass BasePlusCommissionEmployee's version of toString.
Lines 30–31 then assign the reference of subclass object basePlusCommissionEmployee to a superclass CommissionEmployee variable, which lines 32–34 use to invoke method toString. When a superclass variable contains a reference to a subclass object, and that reference is used to call a method, the subclass version of the method is called. Hence, commissionEmployee2.toString() in line 34 actually calls class BasePlusCommissionEmployee's toString method. The Java compiler allows this "crossover" because an object of a subclass is an object of its superclass (but not vice versa). When the compiler encounters a method call made through a variable, the compiler determines if the method can be called by checking the variable's class type. If that class contains the proper method declaration (or inherits one), the call is compiled. At execution time, the type of the object to which the variable refers determines the actual method to use. This process, called dynamic binding, is discussed in detail in Section 10.5.