- 10.1 Introduction
- 10.2 Polymorphism Examples
- 10.3 Demonstrating Polymorphic Behavior
- 10.4 Abstract Classes and Methods
- 10.5 Case Study: Payroll System Using Polymorphism
- 10.6 final Methods and Classes
- 10.7 Case Study: Creating and Using Interfaces
- 10.8 Wrap-Up
10.5 Case Study: Payroll System Using Polymorphism
This section reexamines the hierarchy that we explored throughout Section 9.4. Now we use an abstract method and polymorphism to perform payroll calculations based on an enhanced employee inheritance hierarchy that meets the following requirements:
- A company pays its employees on a weekly basis. The employees are of four types: Salaried employees are paid a fixed weekly salary regardless of the number of hours worked, hourly employees are paid by the hour and receive overtime pay (i.e., 1.5 times their hourly salary rate) for all hours worked in excess of 40 hours, commission employees are paid a percentage of their sales and base-salaried commission employees receive a base salary plus a percentage of their sales. For the current pay period, the company has decided to reward salaried-commission employees by adding 10% to their base salaries. The company wants to write an application that performs its payroll calculations polymorphically.
We use abstract class Employee to represent the general concept of an employee. The classes that extend Employee are SalariedEmployee, CommissionEmployee and HourlyEmployee. Class BasePlusCommissionEmployee—which extends CommissionEmployee—represents the last employee type. The UML class diagram in Fig. 10.2 shows the inheritance hierarchy for our polymorphic employee-payroll application. Abstract class name Employee is italicized—a convention of the UML.
Fig. 10.2 Employee hierarchy UML class diagram.
Abstract superclass Employee declares the "interface" to the hierarchy—that is, the set of methods that a program can invoke on all Employee objects. We use the term "interface" here in a general sense to refer to the various ways programs can communicate with objects of any Employee subclass. Be careful not to confuse the general notion of an "interface" with the formal notion of a Java interface, the subject of Section 10.7. Each employee, regardless of the way his or her earnings are calculated, has a first name, a last name and a social security number, so private instance variables firstName, lastName and socialSecurityNumber appear in abstract superclass Employee.
The following sections implement the Employee class hierarchy of Fig. 10.2. The first section implements abstract superclass Employee. The next four sections each implement one of the concrete classes. The last section implements a test program that builds objects of all these classes and processes those objects polymorphically.
10.5.1 Abstract Superclass Employee
Class Employee (Fig. 10.4) provides methods earnings and toString, in addition to the get and set methods that manipulate Employee's instance variables. An earnings method certainly applies generically to all employees. But each earnings calculation depends on the employee's class. So we declare earnings as abstract in superclass Employee because a default implementation does not make sense for that method—there isn't enough information to determine what amount earnings should return. Each subclass overrides earnings with an appropriate implementation. To calculate an employee's earnings, the program assigns to a superclass Employee variable a reference to the employee's object, then invokes the earnings method on that variable. We maintain an array of Employee variables, each holding a reference to an Employee object. (Of course, there cannot be Employee objects, because Employee is an abstract class. Because of inheritance, however, all objects of all subclasses of Employee may nevertheless be thought of as Employee objects.) The program will iterate through the array and call method earnings for each Employee object. Java processes these method calls polymorphically. Declaring earnings as an abstract method in Employee enables the calls to earnings through Employee variables to compile and forces every direct concrete subclass of Employee to override earnings.
Method toString in class Employee returns a String containing the first name, last name and social security number of the employee. As we'll see, each subclass of Employee overrides method toString to create a String representation of an object of that class that contains the employee's type (e.g., "salaried employee:") followed by the rest of the employee's information.
The diagram in Fig. 10.3 shows each of the five classes in the hierarchy down the left side and methods earnings and toString across the top. For each class, the diagram shows the desired results of each method. We do not list superclass Employee's get and set methods because they're not overridden in any of the subclasses—each of these methods is inherited and used "as is" by each subclass.
Fig. 10.3 Polymorphic interface for the Employee hierarchy classes.
Let's consider class Employee's declaration (Fig. 10.4). The class includes a constructor that takes the first name, last name and social security number as arguments (lines 11–16); get methods that return the first name, last name and social security number (lines 25–28, 37–40 and 49–52, respectively); set methods that set the first name, last name and social security number (lines 19–22, 31–34 and 43–46, respectively); method toString (lines 55–60), which returns the String representation of an Employee; and abstract method earnings (line 63), which will be implemented by each of the concrete subclasses. The Employee constructor does not validate its parameters in this example; normally, such validation should be provided.
Fig 10.4. Employee abstract superclass.
1// Fig. 10.4: Employee.java
2// Employee abstract superclass.
3 4public abstract class Employee
5 { 6 private String firstName; 7 private String lastName; 8 private String socialSecurityNumber; 9 10// three-argument constructor
11 public Employee( String first, String last, String ssn ) 12 { 13 firstName = first; 14 lastName = last; 15 socialSecurityNumber = ssn; 16 }// end three-argument Employee constructor
17 18// set first name
19 public void setFirstName( String first ) 20 { 21 firstName = first;// should validate
22 }// end method setFirstName
23 24// return first name
25 public String getFirstName() 26 { 27 return firstName; 28 }// end method getFirstName
29 30// set last name
31 public void setLastName( String last ) 32 { 33 lastName = last;// should validate
34 }// end method setLastName
35 36// return last name
37 public String getLastName() 38 { 39 return lastName; 40 }// end method getLastName
41 42// set social security number
43 public void setSocialSecurityNumber( String ssn ) 44 { 45 socialSecurityNumber = ssn;// should validate
46 }// end method setSocialSecurityNumber
47 48// return social security number
49 public String getSocialSecurityNumber() 50 { 51 return socialSecurityNumber; 52 }// end method getSocialSecurityNumber
53 54// return String representation of Employee object
55 @Override 56 public String toString() 57 { 58 return String.format("%s %s\nsocial security number: %s"
, 59 getFirstName(), getLastName(), getSocialSecurityNumber() ); 60 }// end method toString
61 62// abstract method overridden by concrete subclasses
63public abstract double earnings();
// no implementation here
64 }// end abstract class Employee
Why did we decide to declare earnings as an abstract method? It simply does not make sense to provide an implementation of this method in class Employee. We cannot calculate the earnings for a general Employee—we first must know the specific type of Employee to determine the appropriate earnings calculation. By declaring this method abstract, we indicate that each concrete subclass must provide an appropriate earnings implementation and that a program will be able to use superclass Employee variables to invoke method earnings polymorphically for any type of Employee.
10.5.2 Concrete Subclass SalariedEmployee
Class SalariedEmployee (Fig. 10.5) extends class Employee (line 4) and overrides abstract method earnings (lines 33–37), which makes SalariedEmployee a concrete class. The class includes a constructor (lines 9–14) that takes a first name, a last name, a social security number and a weekly salary as arguments; a set method to assign a new nonnegative value to instance variable weeklySalary (lines 17–24); a get method to return weeklySalary's value (lines 27–30); a method earnings (lines 33–37) to calculate a SalariedEmployee's earnings; and a method toString (lines 40–45), which returns a String including the employee's type, namely, "salaried employee: " followed by employee-specific information produced by superclass Employee's toString method and SalariedEmployee's getWeeklySalary method. Class SalariedEmployee's constructor passes the first name, last name and social security number to the Employee constructor (line 12) to initialize the private instance variables not inherited from the superclass. Method earnings overrides Employee's abstract method earnings to provide a concrete implementation that returns the SalariedEmployee's weekly salary. If we do not implement earnings, class SalariedEmployee must be declared abstract—otherwise, class SalariedEmployee will not compile. Of course, we want SalariedEmployee to be a concrete class in this example.
Fig 10.5. SalariedEmployee concrete class extends abstract class Employee.
1// Fig. 10.5: SalariedEmployee.java
2// SalariedEmployee concrete class extends abstract class Employee.
3 4public class SalariedEmployee extends Employee
5 { 6 private double weeklySalary; 7 8// four-argument constructor
9 public SalariedEmployee( String first, String last, String ssn, 10 double salary ) 11 { 12 super( first, last, ssn );// pass to Employee constructor
13 setWeeklySalary( salary );// validate and store salary
14 }// end four-argument SalariedEmployee constructor
15 16// set salary
17 public void setWeeklySalary( double salary ) 18 { 19 if ( salary >=0.0
) 20 baseSalary = salary; 21 else 22 throw new IllegalArgumentException( 23"Weekly salary must be >= 0.0"
); 24 }// end method setWeeklySalary
25 26// return salary
27 public double getWeeklySalary() 28 { 29 return weeklySalary; 30 }// end method getWeeklySalary
31 32// calculate earnings; override abstract method earnings in Employee
33@Override
34public double earnings()
35 { 36return getWeeklySalary();
37 }// end method earnings
38 39// return String representation of SalariedEmployee object
40@Override
41public String toString()
42 { 43return String.format( "salaried employee: %s\n%s: $%,.2f"
, 44super.toString(), "weekly salary" , getWeeklySalary() );
45 }// end method toString
46 }// end class SalariedEmployee
Method toString (lines 40–45) overrides Employee method toString. If class SalariedEmployee did not override toString, SalariedEmployee would have inherited the Employee version of toString. In that case, SalariedEmployee's toString method would simply return the employee's full name and social security number, which does not adequately represent a SalariedEmployee. To produce a complete String representation of a SalariedEmployee, the subclass's toString method returns "salaried employee: " followed by the superclass Employee-specific information (i.e., first name, last name and social security number) obtained by invoking the superclass's toString method (line 44)—this is a nice example of code reuse. The String representation of a SalariedEmployee also contains the employee's weekly salary obtained by invoking the class's getWeeklySalary method.
10.5.3 Concrete Subclass HourlyEmployee
Class HourlyEmployee (Fig. 10.6) also extends Employee (line 4). The class includes a constructor (lines 10–16) that takes as arguments a first name, a last name, a social security number, an hourly wage and the number of hours worked. Lines 19–26 and 35–42 declare set methods that assign new values to instance variables wage and hours, respectively. Method setWage (lines 19–26) ensures that wage is nonnegative, and method setHours (lines 35–42) ensures that hours is between 0 and 168 (the total number of hours in a week) inclusive. Class HourlyEmployee also includes get methods (lines 29–32 and 45–48) to return the values of wage and hours, respectively; a method earnings (lines 51–58) to calculate an HourlyEmployee's earnings; and a method toString (lines 61–67), which returns a String containing the employee's type ("hourly employee: ") and the employee-specific information. The HourlyEmployee constructor, like the SalariedEmployee constructor, passes the first name, last name and social security number to the superclass Employee constructor (line 13) to initialize the private instance variables. In addition, method toString calls superclass method toString (line 65) to obtain the Employee-specific information (i.e., first name, last name and social security number)—this is another nice example of code reuse.
Fig 10.6. HourlyEmployee class extends Employee.
1// Fig. 10.6: HourlyEmployee.java
2// HourlyEmployee class extends Employee.
3 4public class HourlyEmployee extends Employee
5 { 6 private double wage;// wage per hour
7 private double hours;// hours worked for week
8 9// five-argument constructor
10 public HourlyEmployee( String first, String last, String ssn, 11 double hourlyWage, double hoursWorked ) 12 { 13 super( first, last, ssn ); 14 setWage( hourlyWage );// validate hourly wage
15 setHours( hoursWorked );// validate hours worked
16 }// end five-argument HourlyEmployee constructor
17 18// set wage
19 public void setWage( double hourlyWage ) 20 { 21 if ( hourlyWage >=0.0
) 22 wage = hourlyWage; 23 else 24 throw new IllegalArgumentException( 25"Hourly wage must be >= 0.0"
); 26 }// end method setWage
27 28// return wage
29 public double getWage() 30 { 31 return wage; 32 }// end method getWage
33 34// set hours worked
35 public void setHours( double hoursWorked ) 36 { 37 if ( ( hoursWorked >=0.0
) && ( hoursWorked <=168.0
) ) 38 hours = hoursWorked; 39 else 40 throw new IllegalArgumentException( 41"Hours worked must be >= 0.0 and <= 168.0"
); 42 }// end method setHours
43 44// return hours worked
45 public double getHours() 46 { 47 return hours; 48 }// end method getHours
49 50// calculate earnings; override abstract method earnings in Employee
51@Override
52public double earnings()
53 { 54if ( getHours() <= 40
)// no overtime
55return getWage() * getHours();
56else
57return 40 * getWage() + ( getHours() -
40 ) * getWage() * 1.5 ;
58 }// end method earnings
59 60// return String representation of HourlyEmployee object
61@Override
62public String toString()
63 { 64return String.format( "hourly employee: %s\n%s: $%,.2f; %s: %,.2f"
, 65super.toString(), "hourly wage" , getWage(),
66"hours worked", getHours() );
67 }// end method toString
68 }// end class HourlyEmployee
10.5.4 Concrete Subclass CommissionEmployee
Class CommissionEmployee (Fig. 10.7) extends class Employee (line 4). The class includes a constructor (lines 10–16) that takes a first name, a last name, a social security number, a sales amount and a commission rate; set methods (lines 19–26 and 35–42) to assign new values to instance variables commissionRate and grossSales, respectively; get methods (lines 29–32 and 45–48) that retrieve the values of these instance variables; method earnings (lines 51–55) to calculate a CommissionEmployee's earnings; and method toString (lines 58–65), which returns the employee's type, namely, "commission employee: " and employee-specific information. The constructor also passes the first name, last name and social security number to Employee's constructor (line 13) to initialize Employee's private instance variables. Method toString calls superclass method toString (line 62) to obtain the Employee-specific information (i.e., first name, last name and social security number).
Fig 10.7. CommissionEmployee class extends Employee.
1// Fig. 10.7: CommissionEmployee.java
2// CommissionEmployee class extends Employee.
3 4public class CommissionEmployee extends Employee
5 { 6 private double grossSales;// gross weekly sales
7 private double commissionRate;// commission percentage
8 9// five-argument constructor
10 public CommissionEmployee( String first, String last, String ssn, 11 double sales, double rate ) 12 { 13 super( first, last, ssn ); 14 setGrossSales( sales ); 15 setCommissionRate( rate ); 16 }// end five-argument CommissionEmployee constructor
17 18// set commission rate
19 public void setCommissionRate( double rate ) 20 { 21 if ( rate >0.0
&& rate <1.0
) 22 commissionRate = rate; 23 else 24 throw new IllegalArgumentException( 25"Commission rate must be > 0.0 and < 1.0"
); 26 }// end method setCommissionRate
27 28// return commission rate
29 public double getCommissionRate() 30 { 31 return commissionRate; 32 }// end method getCommissionRate
33 34// set gross sales amount
35 public void setGrossSales( double sales ) 36 { 37 if ( sales >=0.0
) 38 grossSales = sales; 39 else 40 throw new IllegalArgumentException( 41"Gross sales must be >= 0.0"
); 42 }// end method setGrossSales
43 44// return gross sales amount
45 public double getGrossSales() 46 { 47 return grossSales; 48 }// end method getGrossSales
49 50// calculate earnings; override abstract method earnings in Employee
51@Override
52public double earnings()
53 { 54return getCommissionRate() * getGrossSales();
55 }// end method earnings
56 57// return String representation of CommissionEmployee object
58@Override
59public String toString()
60 { 61return String.format( "%s: %s\n%s: $%,.2f; %s: %.2f"
, 62"commission employee"
, super.toString(), 63"gross sales"
, getGrossSales(), 64"commission rate"
, getCommissionRate() ); 65 }// end method toString
66 }// end class CommissionEmployee
10.5.5 Indirect Concrete Subclass BasePlusCommissionEmployee
Class BasePlusCommissionEmployee (Fig. 10.8) extends class CommissionEmployee (line 4) and therefore is an indirect subclass of class Employee. Class BasePlusCommissionEmployee has a constructor (lines 9–14) that takes as arguments a first name, a last name, a social security number, a sales amount, a commission rate and a base salary. It then passes all of these except the base salary to the CommissionEmployee constructor (line 12) to initialize the inherited members. BasePlusCommissionEmployee also contains a set method (lines 17–24) to assign a new value to instance variable baseSalary and a get method (lines 27–30) to return baseSalary's value. Method earnings (lines 33–37) calculates a BasePlusCommissionEmployee's earnings. Line 36 in method earnings calls superclass CommissionEmployee's earnings method to calculate the commission-based portion of the employee's earnings—this is another nice example of code reuse. BasePlusCommissionEmployee's toString method (lines 40–46) creates a String representation of a BasePlusCommissionEmployee that contains "base-salaried", followed by the String obtained by invoking superclass CommissionEmployee's toString method (another example of code reuse), then the base salary. The result is a String beginning with "base-salaried commission employee" followed by the rest of the BasePlusCommissionEmployee's information. Recall that CommissionEmployee's toString obtains the employee's first name, last name and social security number by invoking the toString method of its superclass (i.e., Employee)—yet another example of code reuse. BasePlusCommissionEmployee's toString initiates a chain of method calls that span all three levels of the Employee hierarchy.
Fig 10.8. BasePlusCommissionEmployee class extends CommissionEmployee.
1// Fig. 10.8: BasePlusCommissionEmployee.java
2// BasePlusCommissionEmployee class extends CommissionEmployee.
3 4public class BasePlusCommissionEmployee extends CommissionEmployee
5 { 6 private double baseSalary;// base salary per week
7 8// six-argument constructor
9 public BasePlusCommissionEmployee( String first, String last, 10 String ssn, double sales, double rate, double salary ) 11 { 12 super( first, last, ssn, sales, rate ); 13 setBaseSalary( salary );// validate and store base salary
14 }// end six-argument BasePlusCommissionEmployee constructor
15 16// set base salary
17 public void setBaseSalary( double salary ) 18 { 19 if ( salary >=0.0
) 20 baseSalary = salary; 21 else 22 throw new IllegalArgumentException( 23"Base salary must be >= 0.0"
); 24 }// end method setBaseSalary
25 26// return base salary
27 public double getBaseSalary() 28 { 29 return baseSalary; 30 }// end method getBaseSalary
31 32// calculate earnings; override method earnings in CommissionEmployee
33@Override
34public double earnings()
35 { 36return getBaseSalary() + super.earnings();
37 }// end method earnings
38 39// return String representation of BasePlusCommissionEmployee object
40@Override
41public String toString()
42 { 43return String.format( "%s %s; %s: $%,.2f"
, 44"base-salaried"
, super.toString(), 45"base salary"
, getBaseSalary() ); 46 }// end method toString
47 }// end class BasePlusCommissionEmployee
10.5.6 Polymorphic Processing, Operator instanceof and Downcasting
To test our Employee hierarchy, the application in Fig. 10.9 creates an object of each of the four concrete classes SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee. The program manipulates these objects nonpolymorphically, via variables of each object's own type, then polymorphically, using an array of Employee variables. While processing the objects polymorphically, the program increases the base salary of each BasePlusCommissionEmployee by 10%—this requires determining the object's type at execution time. Finally, the program polymorphically determines and outputs the type of each object in the Employee array. Lines 9–18 create objects of each of the four concrete Employee subclasses. Lines 22–30 output the String representation and earnings of each of these objects nonpolymorphically. Each object's toString method is called implicitly by printf when the object is output as a String with the %s format specifier.
Fig 10.9. Employee hierarchy test program.
1// Fig. 10.9: PayrollSystemTest.java
2// Employee hierarchy test program.
3 4 public class PayrollSystemTest 5 { 6 public static void main( String[] args ) 7 { 8// create subclass objects
9SalariedEmployee salariedEmployee =
10new SalariedEmployee( "John", "Smith" , "111-11-1111"
,800.00 );
11HourlyEmployee hourlyEmployee =
12new
HourlyEmployee("Karen", "Price" , "222-22-2222"
,16.75
,40 );
13CommissionEmployee commissionEmployee =
14new CommissionEmployee(
15"Sue", "Jones"
,"333-33-3333"
,10000
,.06 );
16BasePlusCommissionEmployee basePlusCommissionEmployee =
17new BasePlusCommissionEmployee(
18"Bob"
,"Lewis"
,"444-44-4444"
,5000
,.04
,300
); 19 20 System.out.println("Employees processed individually:\n"
); 21 22 System.out.printf("%s\n%s: $%,.2f\n\n"
, 23 salariedEmployee,"earned"
, salariedEmployee.earnings() ); 24 System.out.printf("%s\n%s: $%,.2f\n\n"
, 25 hourlyEmployee,"earned"
, hourlyEmployee.earnings() ); 26 System.out.printf("%s\n%s: $%,.2f\n\n"
, 27 commissionEmployee,"earned"
, commissionEmployee.earnings() ); 28 System.out.printf("%s\n%s: $%,.2f\n\n"
, 29 basePlusCommissionEmployee, 30"earned"
, basePlusCommissionEmployee.earnings() ); 31 32// create four-element Employee array
33Employee[] employees = new Employee[ 4 ];
34 35// initialize array with Employees
36employees[
0 ] = salariedEmployee;
37employees[
1 ] = hourlyEmployee;
38employees[
2 ] = commissionEmployee;
39employees[
3 ] = basePlusCommissionEmployee;
40 41 System.out.println("Employees processed polymorphically:\n"
); 42 43// generically process each element in array employees
44 for ( Employee currentEmployee : employees ) 45 { 46 System.out.println(currentEmployee
);// invokes toString
47 48// determine whether element is a BasePlusCommissionEmployee
49 if (currentEmployee instanceof BasePlusCommissionEmployee
) 50 { 51// downcast Employee reference to
52// BasePlusCommissionEmployee reference
53 BasePlusCommissionEmployee employee = 54( BasePlusCommissionEmployee ) currentEmployee
; 55 56 employee.setBaseSalary(1.10
* employee.getBaseSalary() ); 57 58 System.out.printf( 59"new base salary with 10%% increase is: $%,.2f\n"
, 60 employee.getBaseSalary() ); 61 }// end if
62 63 System.out.printf( 64"earned $%,.2f\n\n"
,currentEmployee.earnings()
); 65 }// end for
66 67// get type name of each object in employees array
68for ( int j = 0; j < employees.length; j++ ) 69
System.out.printf( "Employee %d is a %s\n"
, j, 70employees[ j ].getClass().getName() );
71 }// end main
72 }// end class PayrollSystemTest
Creating the Array of Employees
Line 33 declares employees and assigns it an array of four Employee variables. Line 36 assigns the reference to a SalariedEmployee object to employees[0]. Line 37 assigns the reference to an HourlyEmployee object to employees[1]. Line 38 assigns the reference to a CommissionEmployee object to employees[2]. Line 39 assigns the reference to a Base-PlusCommissionEmployee object to employee[3]. These assignments are allowed, because a SalariedEmployee is an Employee, an HourlyEmployee is an Employee, a CommissionEmployee is an Employee and a BasePlusCommissionEmployee is an Employee. Therefore, we can assign the references of SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee objects to superclass Employee variables, even though Employee is an abstract class.
Polymorphically Processing Employees
Lines 44–65 iterate through array employees and invoke methods toString and earnings with Employee variable currentEmployee, which is assigned the reference to a different Employee in the array on each iteration. The output illustrates that the appropriate methods for each class are indeed invoked. All calls to method toString and earnings are resolved at execution time, based on the type of the object to which currentEmployee refers. This process is known as dynamic binding or late binding. For example, line 46 implicitly invokes method toString of the object to which currentEmployee refers. As a result of dynamic binding, Java decides which class's toString method to call at execution time rather than at compile time. Only the methods of class Employee can be called via an Employee variable (and Employee, of course, includes the methods of class Object). A superclass reference can be used to invoke only methods of the superclass—the subclass method implementations are invoked polymorphically.
Performing Type-Specific Operations on BasePlusCommissionEmployees
We perform special processing on BasePlusCommissionEmployee objects—as we encounter these objects at execution time, we increase their base salary by 10%. When processing objects polymorphically, we typically do not need to worry about the "specifics," but to adjust the base salary, we do have to determine the specific type of Employee object at execution time. Line 49 uses the instanceof operator to determine whether a particular Employee object's type is BasePlusCommissionEmployee. The condition in line 49 is true if the object referenced by currentEmployee is a BasePlusCommissionEmployee. This would also be true for any object of a BasePlusCommissionEmployee subclass because of the is-a relationship a subclass has with its superclass. Lines 53–54 downcast currentEmployee from type Employee to type BasePlusCommissionEmployee—this cast is allowed only if the object has an is-a relationship with BasePlusCommissionEmployee. The condition at line 49 ensures that this is the case. This cast is required if we're to invoke subclass BasePlusCommissionEmployee methods getBaseSalary and setBaseSalary on the current Employee object—as you'll see momentarily, attempting to invoke a subclass-only method directly on a superclass reference is a compilation error.
If the instanceof expression in line 49 is true, lines 53–60 perform the special processing required for the BasePlusCommissionEmployee object. Using BasePlusCommissionEmployee variable employee, line 56 invokes subclass-only methods getBaseSalary and setBaseSalary to retrieve and update the employee's base salary with the 10% raise.
Calling earnings Polymorphically
Lines 63–64 invoke method earnings on currentEmployee, which polymorphically calls the appropriate subclass object's earnings method. Obtaining the earnings of the SalariedEmployee, HourlyEmployee and CommissionEmployee polymorphically in lines 63–64 produces the same results as obtaining these employees' earnings individually in lines 22–27. The earnings amount obtained for the BasePlusCommissionEmployee in lines 63–64 is higher than that obtained in lines 28–30, due to the 10% increase in its base salary.
Using Reflection to Get Each Employee's Class Name
Lines 68–70 display each employee's type as a String, using basic features of Java's so-called reflection capabilities. Every object knows its own class and can access this information through the getClass method, which all classes inherit from class Object. Method getClass returns an object of type Class (from package java.lang), which contains information about the object's type, including its class name. Line 70 invokes getClass on the current object to get its runtime class. The result of the getClass call is used to invoke getName to get the object's class name.
Avoiding Compilation Errors with Downcasting
In the previous example, we avoided several compilation errors by downcasting an Employee variable to a BasePlusCommissionEmployee variable in lines 53–54. If you remove the cast operator (BasePlusCommissionEmployee) from line 54 and attempt to assign Employee variable currentEmployee directly to BasePlusCommissionEmployee variable employee, you'll receive an "incompatible types" compilation error. This error indicates that the attempt to assign the reference of superclass object currentEmployee to subclass variable employee is not allowed. The compiler prevents this assignment because a CommissionEmployee is not a BasePlusCommissionEmployee—the is-a relationship applies only between the subclass and its superclasses, not vice versa.
Similarly, if lines 56 and 60 used superclass variable currentEmployee to invoke subclass-only methods getBaseSalary and setBaseSalary, we'd receive "cannot find symbol" compilation errors at these lines. Attempting to invoke subclass-only methods via a superclass variable is not allowed—even though lines 56 and 60 execute only if instanceof in line 49 returns true to indicate that currentEmployee holds a reference to a BasePlusCommissionEmployee object. Using a superclass Employee variable, we can invoke only methods found in class Employee—earnings, toString and Employee's get and set methods.
10.5.7 Summary of the Allowed Assignments Between Superclass and Subclass Variables
Now that you've seen a complete application that processes diverse subclass objects polymorphically, we summarize what you can and cannot do with superclass and subclass objects and variables. Although a subclass object also is a superclass object, the two objects are nevertheless different. As discussed previously, subclass objects can be treated as objects of their superclass. But because the subclass can have additional subclass-only members, assigning a superclass reference to a subclass variable is not allowed without an explicit cast—such an assignment would leave the subclass members undefined for the superclass object.
We've discussed four ways to assign superclass and subclass references to variables of superclass and subclass types:
- Assigning a superclass reference to a superclass variable is straightforward.
- Assigning a subclass reference to a subclass variable is straightforward.
- Assigning a subclass reference to a superclass variable is safe, because the subclass object is an object of its superclass. However, the superclass variable can be used to refer only to superclass members. If this code refers to subclass-only members through the superclass variable, the compiler reports errors.
- Attempting to assign a superclass reference to a subclass variable is a compilation error. To avoid this error, the superclass reference must be cast to a subclass type explicitly. At execution time, if the object to which the reference refers is not a subclass object, an exception will occur. (For more on exception handling, see Chapter 11.) You should use the instanceof operator to ensure that such a cast is performed only if the object is a subclass object.