This section reexamines the CommissionEmployeeBasePlusCommissionEmployee
hierarchy that we explored throughout Section G.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. G.14 shows the inheritance hierarchy for our polymorphic employee-payroll application. Abstract class name Employee
is italicized—a convention of the UML.
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 G.12. 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. G.14. 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.
Class Employee
(Fig. G.16) 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. G.15 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.
Let’s consider class Employee
’s declaration (Fig. G.16). 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.
1 // Fig. G.16: Employee.java
2 // Employee abstract superclass.
3
4 public 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;s
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
social security number: %s",
59 getFirstName(), getLastName(), getSocialSecurityNumber() );
60 } // end method toString
61
62 // abstract method overridden by concrete subclasses
63 public 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
.
Class SalariedEmployee
(Fig. G.17) 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.
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.
1 // Fig. G.17: SalariedEmployee.java
2 // SalariedEmployee concrete class extends abstract class Employee.
3
4 public 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
34 public double earnings()
35 {
36 return getWeeklySalary();
37 } // end method earnings
38
39 // return String representation of SalariedEmployee object
40 @Override
41 public String toString()
42 {
43 return String.format( "salaried employee: %s
%s: $%,.2f",
44 super.toString(), "weekly salary", getWeeklySalary() );
45 } // end method toString
46 } // end class SalariedEmployee
Class HourlyEmployee
(Fig. G.18) 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.
1 // Fig. G.18: HourlyEmployee.java
2 // HourlyEmployee class extends Employee.
3
4 public 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
52 public double earnings()
53 {
54 if ( getHours() <= 40 ) // no overtime
55 return getWage() * getHours();
56 else
57 return 40 * getWage() + ( getHours() - 40 ) * getWage() * 1.5;
58 } // end method earnings
59
60 // return String representation of HourlyEmployee object
61 @Override
62 public String toString()
63 {
64 return String.format( "hourly employee: %s
%s: $%,.2f; %s: %,.2f",
65 super.toString(), "hourly wage", getWage(),
66 "hours worked", getHours() );
67 } // end method toString
68 } // end class HourlyEmployee
Class CommissionEmployee
(Fig. G.19) 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).
1 // Fig. G.19: CommissionEmployee.java
2 // CommissionEmployee class extends Employee.
3
4 public 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
52 public double earnings()
53 {
54 return getCommissionRate() * getGrossSales();
55 } // end method earnings
56
57 // return String representation of CommissionEmployee object
58 @Override
59 public String toString()
60 {
61 return String.format( "%s: %s
%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
Class BasePlusCommissionEmployee
(Fig. G.20) 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
1 // Fig. G.20: BasePlusCommissionEmployee.java
2 // BasePlusCommissionEmployee class extends CommissionEmployee.
3
4 public 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
34 public double earnings()
35 {
36 return getBaseSalary() + super.earnings();
37 } // end method earnings
38
39 // return String representation of BasePlusCommissionEmployee object
40 @Override
41 public String toString()
42 {
43 return String.format( "%s %s; %s: $%,.2f",
44 "base-salaried", super.toString(),
45 "base salary", getBaseSalary() );
46 } // end method toString
47 } // end class BasePlusCommissionEmployee
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.
To test our Employee
hierarchy, the application in Fig. G.21 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.
1 // Fig. G.21: 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
9 SalariedEmployee salariedEmployee =
10 new SalariedEmployee( "John", "Smith", "111-11-1111", 800.00 );
11 HourlyEmployee hourlyEmployee =
12 new HourlyEmployee( "Karen", "Price", "222-22-2222", 16.75, 40 );
13 CommissionEmployee commissionEmployee =
14 new CommissionEmployee(
15 "Sue", "Jones", "333-33-3333", 10000, .06 );
16 BasePlusCommissionEmployee basePlusCommissionEmployee =
17 new BasePlusCommissionEmployee(
18 "Bob", "Lewis", "444-44-4444", 5000, .04, 300 );
19
20 System.out.println( "Employees processed individually:
" );
21
22 System.out.printf( "%s
%s: $%,.2f
",
23 salariedEmployee, "earned", salariedEmployee.earnings() );
24 System.out.printf( "%s
%s: $%,.2f
",
25 hourlyEmployee, "earned", hourlyEmployee.earnings() );
26 System.out.printf( "%s
%s: $%,.2f
",
27 commissionEmployee, "earned", commissionEmployee.earnings() );
28 System.out.printf( "%s
%s: $%,.2f
",
29 basePlusCommissionEmployee,
30 "earned", basePlusCommissionEmployee.earnings() );
31
32 // create four-element Employee array
33 Employee[] employees = new Employee[ 4 ];
34
35 // initialize array with Employees
36 employees[ 0 ] = salariedEmployee;
37 employees[ 1 ] = hourlyEmployee;
38 employees[ 2 ] = commissionEmployee;
39 employees[ 3 ] = basePlusCommissionEmployee;
40
41 System.out.println( "Employees processed polymorphically:
" );
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
",
60 employee.getBaseSalary() );
61 } // end if
62
63 System.out.printf(
64 "earned $%,.2f
", currentEmployee.earnings() );
65 } // end for
66
67 // get type name of each object in employees array
68 for ( int j = 0; j < employees.length; j++ )
69 System.out.printf( "Employee %d is a %s
", j,
70 employees[ j ].getClass().getName() );
71 } // end main
72 } // end class PayrollSystemTest
Employees processed individually:
salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
earned: $800.00
hourly employee: Karen Price
social security number: 222-22-2222
hourly wage: $16.75; hours worked: 40.00
earned: $670.00
commission employee: Sue Jones
social security number: 333-33-3333
gross sales: $10,000.00; commission rate: 0.06
earned: $600.00
base-salaried commission employee: Bob Lewis
social security number: 444-44-4444
gross sales: $5,000.00; commission rate: 0.04; base salary: $300.00
earned: $500.00
Employees processed polymorphically:
salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
earned $800.00
hourly employee: Karen Price
social security number: 222-22-2222
hourly wage: $16.75; hours worked: 40.00
earned $670.00
commission employee: Sue Jones
social security number: 333-33-3333
gross sales: $10,000.00; commission rate: 0.06
earned $600.00
base-salaried commission employee: Bob Lewis
social security number: 444-44-4444
gross sales: $5,000.00; commission rate: 0.04; base salary: $300.00
new base salary with 10% increase is: $330.00
earned $530.00
Employee 0 is a SalariedEmployee
Employee 1 is a HourlyEmployee
Employee 2 is a CommissionEmployee
Employee 3 is a BasePlusCommissionEmployee
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 BasePlusCommissionEmployee
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.
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.
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.
Assigning a superclass variable to a subclass variable (without an explicit cast) is a compilation error.
Software Engineering Observation G.7
If a subclass object’s reference has been assigned to a variable of one of its direct or indirect superclasses at execution time, it’s acceptable to downcast the reference stored in that superclass variable back to a subclass-type reference. Before performing such a cast, use the instanceof
operator to ensure that the object is indeed an object of an appropriate subclass.
When downcasting a reference, a ClassCastException
occurs if the referenced object at execution time does not have an is-a relationship with the type specified in the cast operator.
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.
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.
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.
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.
Software Engineering Observation G.8
Although the actual method that’s called depends on the runtime type of the object to which a variable refers, a variable can be used to invoke only those methods that are members of that variable’s type, which the compiler verifies.
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:
1. Assigning a superclass reference to a superclass variable is straightforward.
2. Assigning a subclass reference to a subclass variable is straightforward.
3. 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.
4. 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 Appendix H.) You should use the instanceof
operator to ensure that such a cast is performed only if the object is a subclass object.
We saw in Sections D.3 and D.10 that variables can be declared final
to indicate that they cannot be modified after they’re initialized—such variables represent constant values. It’s also possible to declare methods, method parameters and classes with the final
modifier.
A final method in a superclass cannot be overridden in a subclass—this guarantees that the final
method implementation will be used by all direct and indirect subclasses in the hierarchy. Methods that are declared private
are implicitly final
, because it’s not possible to override them in a subclass. Methods that are declared static
are also implicitly final
. A final
method’s declaration can never change, so all subclasses use the same method implementation, and calls to final
methods are resolved at compile time—this is known as static binding.
A final class that’s declared final
cannot be a superclass (i.e., a class cannot extend a final
class). All methods in a final
class are implicitly final
. Class String
is an example of a final
class. If you were allowed to create a subclass of String
, objects of that subclass could be used wherever String
s are expected. Since class String
cannot be extended, programs that use String
s can rely on the functionality of String
objects as specified in the Java API. Making the class final
also prevents programmers from creating subclasses that might bypass security restrictions. For more insights on the use of keyword final
, visit
and
Attempting to declare a subclass of a final
class is a compilation error.
Software Engineering Observation G.9
In the Java API, the vast majority of classes are not declared final
. This enables inheritance and polymorphism. However, in some cases, it’s important to declare classes final
—typically for security reasons.
Our next example (Figs. G.23–G.27) reexamines the payroll system of Section G.10. Suppose that the company involved wishes to perform several accounting operations in a single accounts payable application—in addition to calculating the earnings that must be paid to each employee, the company must also calculate the payment due on each of several invoices (i.e., bills for goods purchased). Though applied to unrelated things (i.e., employees and invoices), both operations have to do with obtaining some kind of payment amount. For an employee, the payment refers to the employee’s earnings. For an invoice, the payment refers to the total cost of the goods listed on the invoice. Can we calculate such different things as the payments due for employees and invoices in a single application polymorphically? Does Java offer a capability requiring that unrelated classes implement a set of common methods (e.g., a method that calculates a payment amount)? Java interfaces offer exactly this capability.
Interfaces define and standardize the ways in which things such as people and systems can interact with one another. For example, the controls on a radio serve as an interface between radio users and a radio’s internal components. The controls allow users to perform only a limited set of operations (e.g., change the station, adjust the volume, choose between AM and FM), and different radios may implement the controls in different ways (e.g., using push buttons, dials, voice commands). The interface specifies what operations a radio must permit users to perform but does not specify how the operations are performed.
Software objects also communicate via interfaces. A Java interface describes a set of methods that can be called on an object to tell it, for example, to perform some task or return some piece of information. The next example introduces an interface named Payable
to describe the functionality of any object that must be capable of being paid and thus must offer a method to determine the proper payment amount due. An interface declaration begins with the keyword interface and contains only constants and abstract
methods. Unlike classes, all interface members must be public
, and interfaces may not specify any implementation details, such as concrete method declarations and instance variables. All methods declared in an interface are implicitly public abstract
methods, and all fields are implicitly public
, static
and final
. [Note: As of Java SE 5, it became a better programming practice to declare sets of constants as enumerations with keyword enum
. See Section D.10 for an introduction to enum
and Section F.8 for additional enum
details.]
According to Chapter 9 of the Java Language Specification, it’s proper style to declare an interface’s methods without keywords public
and abstract
, because they’re redundant in interface method declarations. Similarly, constants should be declared without keywords public
, static
and final
, because they, too, are redundant.
To use an interface, a concrete class must specify that it implements the interface and must declare each method in the interface with the signature specified in the interface declaration. To specify that a class implements an interface add the implements
keyword and the name of the interface to the end of your class declaration’s first line. A class that does not implement all the methods of the interface is an abstract class and must be declared abstract
. Implementing an interface is like signing a contract with the compiler that states, “I will declare all the methods specified by the interface or I will declare my class abstract
.”
Failing to implement any method of an interface in a concrete class that implements
the interface results in a compilation error indicating that the class must be declared abstract
.
An interface is often used when disparate (i.e., unrelated) classes need to share common methods and constants. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to the same method calls. You can create an interface that describes the desired functionality, then implement this interface in any classes that require that functionality. For example, in the accounts payable application developed in this section, we implement interface Payable
in any class that must be able to calculate a payment amount (e.g., Employee
, Invoice
).
An interface is often used in place of an abstract
class when there’s no default implementation to inherit—that is, no fields and no default method implementations. Like public abstract
classes, interfaces are typically public
types. Like a public
class, a public
interface must be declared in a file with the same name as the interface and the .java
file-name extension.
We’ll see in Appendix J, the notion of “tagging interfaces”—empty interfaces that have no methods or constant values. They’re used to add is-a relationships to classes. For example, in Appendix J we’ll discuss a mechanism called object serialization, which can convert objects to byte representations and can convert those byte representations back to objects. To enable this mechanism to work with your objects, you simply have to mark them as Serializable
by adding implements Serializable
to the end of your class declaration’s first line. Then, all the objects of your class have the is-a relationship with Serializable
.
To build an application that can determine payments for employees and invoices alike, we first create interface Payable
, which contains method getPaymentAmount
that returns a double
amount that must be paid for an object of any class that implements the interface. Method getPaymentAmount
is a general-purpose version of method earnings
of the Employee
hierarchy—method earnings
calculates a payment amount specifically for an Employee
, while getPaymentAmount
can be applied to a broad range of unrelated objects. After declaring interface Payable
, we introduce class Invoice
, which implements
interface Payable
. We then modify class Employee
such that it also implements interface Payable
. Finally, we update Employee
subclass SalariedEmployee
to “fit” into the Payable
hierarchy by renaming SalariedEmployee
method earnings
as getPaymentAmount
.
When declaring a method in an interface, choose a method name that describes the method’s purpose in a general manner, because the method may be implemented by many unrelated classes.
Classes Invoice
and Employee
both represent things for which the company must be able to calculate a payment amount. Both classes implement the Payable
interface, so a program can invoke method getPaymentAmount
on Invoice
objects and Employee
objects alike. As we’ll soon see, this enables the polymorphic processing of Invoice
s and Employee
s required for the company’s accounts payable application.
The UML class diagram in Fig. G.22 shows the hierarchy used in our accounts payable application. The hierarchy begins with interface Payable
. The UML distinguishes an interface from other classes by placing the word “interface” in guillemets (« and ») above the interface name. The UML expresses the relationship between a class and an interface through a relationship known as realization. A class is said to “realize,” or implement, the methods of an interface. A class diagram models a realization as a dashed arrow with a hollow arrowhead pointing from the implementing class to the interface. The diagram in Fig. G.22 indicates that classes Invoice
and Employee
each realize (i.e., implement) interface Payable
. As in the class diagram of Fig. G.14, class Employee
appears in italics, indicating that it’s an abstract class. Concrete class SalariedEmployee
extends Employee
and inherits its superclass’s realization relationship with interface Payable
.
The declaration of interface Payable
begins in Fig. G.23 at line 4. Interface Payable
contains public abstract
method getPaymentAmount
(line 6). The method is not explicitly declared public
or abstract
. Interface methods are always public
and abstract
, so they do not need to be declared as such. Interface Payable
has only one method—interfaces can have any number of methods. In addition, method getPaymentAmount
has no parameters, but interface methods can have parameters. Interfaces may also contain fields that are implicitly final
and static
.
1 // Fig. G.23: Payable.java
2 // Payable interface declaration.
3
4 public interface Payable
5 {
6 double getPaymentAmount(); // calculate payment; no implementation
7 } // end interface Payable
We now create class Invoice
(Fig. G.24) to represent a simple invoice that contains billing information for only one kind of part. The class declares private
instance variables partNumber
, partDescription
, quantity
and pricePerItem
(in lines 6–9) that indicate the part number, a description of the part, the quantity of the part ordered and the price per item. Class Invoice
also contains a constructor (lines 12–19), get and set methods (lines 22–74) that manipulate the class’s instance variables and a toString
method (lines 77–83) that returns a String
representation of an Invoice
object. Methods setQuantity
(lines 46–52) and setPricePerItem
(lines 61–68) ensure that quantity
and pricePerItem
obtain only nonnegative values.
1 // Fig. G.24: Invoice.java
2 // Invoice class that implements Payable.
3
4 public class Invoice implements Payable
5 {
6 private String partNumber;
7 private String partDescription;
8 private int quantity;2
9 private double pricePerItem;
10
11 // four-argument constructor
12 public Invoice( String part, String description, int count,
13 double price )
14 {
15 partNumber = part;
16 partDescription = description;
17 setQuantity( count ); // validate and store quantity
18 setPricePerItem( price ); // validate and store price per item
19 } // end four-argument Invoice constructor
20
21 // set part number
22 public void setPartNumber( String part )
23 {
24 partNumber = part; // should validate
25 } // end method setPartNumber
26
27 // get part number
28 public String getPartNumber()
29 {
30 return partNumber;
31 } // end method getPartNumber
32
33 // set description
34 public void setPartDescription( String description )
35 {
36 partDescription = description; // should validate
37 } // end method setPartDescription
38
39 // get description
40 public String getPartDescription()
41 {
42 return partDescription;
43 } // end method getPartDescription
44
45 // set quantity
46 public void setQuantity( int count )
47 {
48 if ( count >= 0 )
49 quantity = count;
50 else
51 throw new IllegalArgumentException( "Quantity must be >= 0" );
52 } // end method setQuantity
53
54 // get quantity
55 public int getQuantity()
56 {
57 return quantity;
58 } // end method getQuantity
59
60 // set price per item
61 public void setPricePerItem( double price )
62 {
63 if ( price >= 0.0 )
64 pricePerItem = price;
65 else
66 throw new IllegalArgumentException(
67 "Price per item must be >= 0" );
68 } // end method setPricePerItem
69
70 // get price per item
71 public double getPricePerItem()
72 {
73 return pricePerItem;
74 } // end method getPricePerItem
75
76 // return String representation of Invoice object
77 @Override
78 public String toString()
79 {
80 return String.format( "%s:
%s: %s (%s)
%s: %d
%s: $%,.2f",
81 "invoice", "part number", getPartNumber(), getPartDescription(),
82 "quantity", getQuantity(), "price per item", getPricePerItem() );
83 } // end method toString
84
85 // method required to carry out contract with interface Payable
86 @Override
87 public double getPaymentAmount()
88 {
89 return getQuantity() * getPricePerItem(); // calculate total cost
90 } // end method getPaymentAmount
91 } // end class Invoice
Line 4 indicates that class Invoice
implements interface Payable
. Like all classes, class Invoice
also implicitly extends Object
. Java does not allow subclasses to inherit from more than one superclass, but it allows a class to inherit from one superclass and implement as many interfaces as it needs. To implement more than one interface, use a comma-separated list of interface names after keyword implements
in the class declaration, as in:
public class ClassName extends SuperclassName implements FirstInterface,
SecondInterface, ...
Software Engineering Observation G.10
All objects of a class that implement multiple interfaces have the is-a relationship with each implemented interface type.
Class Invoice
implements the one method in interface Payable
—method getPaymentAmount
is declared in lines 86–90. The method calculates the total payment required to pay the invoice. The method multiplies the values of quantity
and pricePerItem
(obtained through the appropriate get methods) and returns the result (line 89). This method satisfies the implementation requirement for this method in interface Payable
—we’ve fulfilled the interface contract with the compiler.
We now modify class Employee
such that it implements interface Payable
. Figure G.25 contains the modified class, which is identical to that of Fig. G.16 with two exceptions. First, line 4 of Fig. G.25 indicates that class Employee
now implements
interface Payable
. So we must rename earnings
to getPaymentAmount
throughout the Employee
hierarchy. As with method earnings
in the version of class Employee
in Fig. G.16, however, it does not make sense to implement method getPaymentAmount
in class Employee
because we cannot calculate the earnings payment owed to a general Employee
—we must first know the specific type of Employee
. In Fig. G.16, we declared method earnings
as abstract
for this reason, so class Employee
had to be declared abstract
. This forced each Employee
concrete subclass to override earnings
with an implementation.
In Fig. G.25, we handle this situation differently. Recall that when a class implements an interface, it makes a contract with the compiler stating either that the class will implement each of the methods in the interface or that the class will be declared abstract
. If the latter option is chosen, we do not need to declare the interface methods as abstract
in the abstract
class—they’re already implicitly declared as such in the interface. Any concrete subclass of the abstract
class must implement the interface methods to fulfill the superclass’s contract with the compiler. If the subclass does not do so, it too must be declared abstract
. As indicated by the comments in lines 62–63, class Employee
of Fig. G.25 does not implement method getPaymentAmount
, so the class is declared abstract
. Each direct Employee
subclass inherits the superclass’s contract to implement method getPaymentAmount
and thus must implement this method to become a concrete class for which objects can be instantiated. A class that extends one of Employee
’s concrete subclasses will inherit an implementation of getPaymentAmount
and thus will also be a concrete class.
1 // Fig. G.25: Employee.java
2 // Employee abstract superclass that implements Payable.
3
4 public abstract class Employee implements Payable
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
social security number: %s",
59 getFirstName(), getLastName(), getSocialSecurityNumber() );
60 } // end method toString
61
62 // Note: We do not implement Payable method getPaymentAmount here so
63 // this class must be declared abstract to avoid a compilation error.
64 } // end abstract class Employee
Figure G.26 contains a modified SalariedEmployee
class that extends Employee
and fulfills superclass Employee
’s contract to implement Payable
method getPaymentAmount
. This version of SalariedEmployee
is identical to that of Fig. G.17, but it replaces method earnings
with method getPaymentAmount
(lines 34–38). Recall that the Payable
version of the method has a more general name to be applicable to possibly disparate classes. The remaining Employee
subclasses (e.g., HourlyEmployee
, CommissionEmployee
and BasePlusCommissionEmployee
) also must be modified to contain method getPaymentAmount
in place of earnings
to reflect the fact that Employee
now implements Payable
. We leave these modifications as an exercise (Exercise G.16) and use only SalariedEmployee
in our test program here. Exercise G.17 asks you to implement interface Payable
in the entire Employee
class hierarchy of Figs. G.16–G.21 without modifying the Employee
subclasses.
When a class implements an interface, the same is-a relationship provided by inheritance applies. Class Employee
implements Payable
, so we can say that an Employee
is a Payable
. In fact, objects of any classes that extend Employee
are also Payable
objects. SalariedEmployee
objects, for instance, are Payable
objects. Objects of any subclasses of the class that implements
the interface can also be thought of as objects of the interface type. Thus, just as we can assign the reference of a SalariedEmployee
object to a superclass Employee
variable, we can assign the reference of a SalariedEmployee
object to an interface Payable
variable. Invoice
implements Payable
, so an Invoice
object also is a Payable
object, and we can assign the reference of an Invoice
object to a Payable
variable.
Software Engineering Observation G.11
When a method parameter is declared with a superclass or interface type, the method processes the object received as an argument polymorphically.
Software Engineering Observation G.12
Using a superclass reference, we can polymorphically invoke any method declared in the superclass and its superclasses (e.g., class Object
). Using an interface reference, we can polymorphically invoke any method declared in the interface, its superinterfaces (one interface can extend another) and in class Object
—a variable of an interface type must refer to an object to call methods, and all objects have the methods of class Object
.
1 // Fig. G.26: SalariedEmployee.java
2 // SalariedEmployee class extends Employee, which implements Payable.
3
4 public 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; implement interface Payable method that was
33 // abstract in superclass Employee
34 @Override
35 public double getPaymentAmount()
36 {
37 return getWeeklySalary();
38 } // end method getPaymentAmount
39
40 // return String representation of SalariedEmployee object
41 @Override
42 public String toString()
43 {
44 return String.format( "salaried employee: %s
%s: $%,.2f",
45 super.toString(), "weekly salary", getWeeklySalary() );
46 } // end method toString
47 } // end class SalariedEmployee
PayableInterfaceTest
(Fig. G.27) illustrates that interface Payable
can be used to process a set of Invoice
s and Employee
s polymorphically in a single application. Line 9 declares payableObjects
and assigns it an array of four Payable
variables. Lines 12–13 assign the references of Invoice
objects to the first two elements of payableObjects
. Lines 14–17 then assign the references of SalariedEmployee
objects to the remaining two elements of payableObjects
. These assignments are allowed because an Invoice
is a Payable
, a SalariedEmployee
is an Employee
and an Employee
is a Payable
. Lines 23–29 use the enhanced for
statement to polymorphically process each Payable
object in payableObjects
, printing the object as a String
, along with the payment amount due. Line 27 invokes method toString
via a Payable
interface reference, even though toString
is not declared in interface Payable
—all references (including those of interface types) refer to objects that extend Object
and therefore have a toString
method. (Method toString
also can be invoked implicitly here.) Line 28 invokes Payable
method getPaymentAmount
to obtain the payment amount for each object in payableObjects
, regardless of the actual type of the object. The output reveals that the method calls in lines 27–28 invoke the appropriate class’s implementation of methods toString
and getPaymentAmount
. For instance, when currentPayable
refers to an Invoice
during the first iteration of the for
loop, class Invoice
’s toString
and getPaymentAmount
execute.
1 // Fig. G.27: PayableInterfaceTest.java
2 // Tests interface Payable.
3
4 public class PayableInterfaceTest
5 {
6 public static void main( String[] args )
7 {
8 // create four-element Payable array
9 Payable[] payableObjects = new Payable[ 4 ];
10
11 // populate array with objects that implement Payable
12 payableObjects[ 0 ] = new Invoice( "01234", "seat", 2, 375.00 );
13 payableObjects[ 1 ] = new Invoice( "56789", "tire", 4, 79.95 );
14 payableObjects[ 2 ] =
15 new SalariedEmployee( "John", "Smith", "111-11-1111", 800.00 );
16 payableObjects[ 3 ] =
17 new SalariedEmployee( "Lisa", "Barnes", "888-88-8888", 1200.00 );
18
19 System.out.println(
20 "Invoices and Employees processed polymorphically:
" );
21
22 // generically process each element in array payableObjects
23 for ( Payable currentPayable : payableObjects )
24 {
25 // output currentPayable and its appropriate payment amount
26 System.out.printf( "%s
%s: $%,.2f
",
27 currentPayable.toString(),
28 "payment due", currentPayable.getPaymentAmount() );
29 } // end for
30 } // end main
31 } // end class PayableInterfaceTest
Invoices and Employees processed polymorphically:
invoice:
part number: 01234 (seat)
quantity: 2
price per item: $375.00
payment due: $750.00
invoice:
part number: 56789 (tire)
quantity: 4
price per item: $79.95
payment due: $319.80
salaried employee: John Smith
social security number: 111-11-1111
weekly salary: $800.00
payment due: $800.00
salaried employee: Lisa Barnes
social security number: 888-88-8888
weekly salary: $1,200.00
payment due: $1,200.00
In this section, we overview several common interfaces found in the Java API. The power and flexibility of interfaces is used frequently throughout the Java API. These interfaces are implemented and used in the same manner as the interfaces you create (e.g., interface Payable
in Section G.12.2). The Java API’s interfaces enable you to use your own classes within the frameworks provided by Java, such as comparing objects of your own types and creating tasks that can execute concurrently with other tasks in the same program. Figure G.28 overviews a few commonly used interfaces of the Java API.
We introduced inheritance—the ability to create classes by absorbing an existing class’s members and embellishing them with new capabilities. You learned the notions of superclasses and subclasses and used keyword extends
to create a subclass that inherits members from a superclass. We showed how to use the @Override
annotation to prevent unintended overloading by indicating that a method overrides a superclass method. We introduced the access modifier protected
; subclass methods can directly access protected
superclass members. You learned how to use super
to access overridden superclass members. You also saw how constructors are used in inheritance hierarchies. Next, you learned about the methods of class Object
, the direct or indirect superclass of all Java classes.
We discussed polymorphism—the ability to process objects that share the same superclass in a class hierarchy as if they’re all objects of the superclass. We considered how polymorphism makes systems extensible and maintainable, then demonstrated how to use overridden methods to effect polymorphic behavior. We introduced abstract classes, which allow you to provide an appropriate superclass from which other classes can inherit. You learned that an abstract class can declare abstract methods that each subclass must implement to become a concrete class and that a program can use variables of an abstract class to invoke the subclasses’ implementations of abstract methods polymorphically. You also learned how to determine an object’s type at execution time. We discussed the concepts of final
methods and classes. Finally, we discussed declaring and implementing an interface as another way to achieve polymorphic behavior.
You should now be familiar with classes, objects, encapsulation, inheritance, polymorphism and interfaces—the most essential aspects of object-oriented programming.
Next, you’ll learn about exceptions, useful for handling errors during a program’s execution. Exception handling helps you build more robust programs.
G.1 Fill in the blanks in each of the following statements:
a) __________ is a form of software reusability in which new classes acquire the members of existing classes and embellish those classes with new capabilities.
b) A superclass’s __________ members can be accessed in the superclass declaration and in subclass declarations.
c) In a(n) __________ relationship, an object of a subclass can also be treated as an object of its superclass.
d) In a(n) __________ relationship, a class object has references to objects of other classes as members.
e) In single inheritance, a class exists in a(n) __________ relationship with its subclasses.
f) A superclass’s __________ members are accessible anywhere that the program has a reference to an object of that superclass or to an object of one of its subclasses.
g) When an object of a subclass is instantiated, a superclass __________ is called implicitly or explicitly.
h) Subclass constructors can call superclass constructors via the __________ keyword.
G.2 State whether each of the following is true or false. If a statement is false, explain why.
a) Superclass constructors are not inherited by subclasses.
b) A has-a relationship is implemented via inheritance.
c) A Car
class has an is-a relationship with the SteeringWheel
and Brakes
classes.
d) When a subclass redefines a superclass method by using the same signature, the subclass is said to overload that superclass method.
G.3 Fill in the blanks in each of the following statements:
a) If a class contains at least one abstract method, it’s a(n) __________ class.
b) Classes from which objects can be instantiated are called __________ classes.
c) __________ involves using a superclass variable to invoke methods on superclass and subclass objects, enabling you to “program in the general.”
d) Methods that are not interface methods and that do not provide implementations must be declared using keyword __________.
e) Casting a reference stored in a superclass variable to a subclass type is called __________.
G.4 State whether each of the statements that follows is true or false. If false, explain why.
a) All methods in an abstract
class must be declared as abstract
methods.
b) Invoking a subclass-only method through a subclass variable is not allowed.
c) If a superclass declares an abstract
method, a subclass must implement that method.
d) An object of a class that implements an interface may be thought of as an object of that interface type.
a) Inheritance.
b) public
and protected
.
c) is-a or inheritance.
d) has-a or composition.
e) hierarchical.
f) public
.
g) constructor.
h) super
.
a) True.
b) False. A has-a relationship is implemented via composition. An is-a relationship is implemented via inheritance.
c) False. This is an example of a has-a relationship. Class Car
has an is-a relationship with class Vehicle
.
d) False. This is known as overriding, not overloading—an overloaded method has the same name, but a different signature.
a) abstract.
b) concrete.
c) Polymorphism.
d) abstract
.
e) downcasting.
a) False. An abstract class can include methods with implementations and abstract
methods.
b) False. Trying to invoke a subclass-only method with a superclass variable is not allowed.
c) False. Only a concrete subclass must implement the method.
d) True.
G.5 Discuss the ways in which inheritance promotes software reuse, saves time during program development and helps prevent errors.
G.6 Draw an inheritance hierarchy for students at a university similar to the hierarchy shown in Fig. G.2. Use Student
as the superclass of the hierarchy, then extend Student
with classes UndergraduateStudent
and GraduateStudent
. Continue to extend the hierarchy as deep (i.e., as many levels) as possible. For example, Freshman
, Sophomore, Junior
and Senior
might extend UndergraduateStudent
, and DoctoralStudent
and MastersStudent
might be subclasses of GraduateStudent
. After drawing the hierarchy, discuss the relationships that exist between the classes. [Note: You do not need to write any code for this exercise.]
G.7 Some programmers prefer not to use protected
access, because they believe it breaks the encapsulation of the superclass. Discuss the relative merits of using protected
access vs. using private
access in superclasses.
G.8 Write an inheritance hierarchy for classes Quadrilateral
, Trapezoid
, Parallelogram
, Rectangle
and Square
. Use Quadrilateral
as the superclass of the hierarchy. Create and use a Point
class to represent the points in each shape. Make the hierarchy as deep (i.e., as many levels) as possible. Specify the instance variables and methods for each class. The private
instance variables of Quadrilateral
should be the x-y coordinate pairs for the four endpoints of the Quadrilateral
. Write a program that instantiates objects of your classes and outputs each object’s area (except Quadrilateral
).
G.9 How does polymorphism enable you to program “in the general” rather than “in the specific”? Discuss the key advantages of programming “in the general.”
G.10 What are abstract methods? Describe the circumstances in which an abstract method would be appropriate.
G.11 How does polymorphism promote extensibility?
G.12 Discuss four ways in which you can assign superclass and subclass references to variables of superclass and subclass types.
G.13 Compare and contrast abstract classes and interfaces. Why would you use an abstract class? Why would you use an interface?
G.14 (Payroll System Modification) Modify the payroll system of Figs. G.16–G.21 to include private
instance variable birthDate
in class Employee
. Use class Date
of Fig. F.7 to represent an employee’s birthday. Add get methods to class Date
. Assume that payroll is processed once per month. Create an array of Employee
variables to store references to the various employee objects. In a loop, calculate the payroll for each Employee
(polymorphically), and add a $100.00 bonus to the person’s payroll amount if the current month is the one in which the Employee
’s birthday occurs.
G.15 (Payroll System Modification) Modify the payroll system of Figs. G.16–G.21 to include an additional Employee
subclass PieceWorker
that represents an employee whose pay is based on the number of pieces of merchandise produced. Class PieceWorker
should contain private
instance variables wage
(to store the employee’s wage per piece) and pieces
(to store the number of pieces produced). Provide a concrete implementation of method earnings
in class PieceWorker
that calculates the employee’s earnings by multiplying the number of pieces produced by the wage per piece. Create an array of Employee
variables to store references to objects of each concrete class in the new Employee
hierarchy. For each Employee
, display its String
representation and earnings.
G.16 (Accounts Payable System Modification) In this exercise, we modify the accounts payable application of Figs. G.23–G.27 to include the complete functionality of the payroll application of Figs. G.16–G.21. The application should still process two Invoice
objects, but now should process one object of each of the four Employee
subclasses. If the object currently being processed is a BasePlusCommissionEmployee
, the application should increase the BasePlusCommissionEmployee
’s base salary by 10%. Finally, the application should output the payment amount for each object. Complete the following steps to create the new application:
a) Modify classes HourlyEmployee
(Fig. G.18) and CommissionEmployee
(Fig. G.19) to place them in the Payable
hierarchy as subclasses of the version of Employee
(Fig. G.25) that implements Payable
. [Hint: Change the name of method earnings
to getPaymentAmount
in each subclass so that the class satisfies its inherited contract with interface Payable
.]
b) Modify class BasePlusCommissionEmployee
(Fig. G.20) such that it extends the version of class CommissionEmployee
created in part (a).
c) Modify PayableInterfaceTest
(Fig. G.27) to polymorphically process two Invoice
s, one SalariedEmployee
, one HourlyEmployee
, one CommissionEmployee
and one BasePlusCommissionEmployee
. First output a String
representation of each Payable
object. Next, if an object is a BasePlusCommissionEmployee
, increase its base salary by 10%. Finally, output the payment amount for each Payable
object.
G.17 (Accounts Payable System Modification) It’s possible to include the functionality of the payroll application (Figs. G.16–G.21) in the accounts payable application without modifying Employee
subclasses SalariedEmployee
, HourlyEmployee
, CommissionEmployee
or BasePlusCommissionEmplyee
. To do so, you can modify class Employee
(Fig. G.16) to implement interface Payable
and declare method getPaymentAmount
to invoke method earnings
. Method getPaymentAmount
would then be inherited by the subclasses in the Employee
hierarchy. When getPaymentAmount
is called for a particular subclass object, it polymorphically invokes the appropriate earnings
method for that subclass. Reimplement Exercise G.16 using the original Employee
hierarchy from the payroll application of Figs. G.16–G.21. Modify class Employee
as described in this exercise, and do not modify any of class Employee
’s subclasses.