G. Object-Oriented Programming: Inheritance and Polymorphism


Objectives

In this appendix you’ll:

Image Learn how inheritance promotes software resuse.

Image Understand the relationships between superclasses and subclasses.

Image Use keyword extends to effect inheritance.

Image Use protected to give subclass methods access to superclass members.

Image Reference superclass members with super.

Image Learn the methods of class Object.

Image Learn the concept of polymorphism.

Image Use overridden methods to effect polymorphism.

Image Distinguish between abstract and concrete classes.

Image Declare abstract methods to create abstract classes.

Image Learn how polymorphism makes systems extensible and maintainable.

Image Determine an object’s type at execution time.

Image Declare and implement interfaces.


G.1 Introduction to Inheritance

The first part of this appendix continues our discussion of object-oriented programming (OOP) by introducing one of its primary capabilities—inheritance, which is a form of software reuse in which a new class is created by absorbing an existing class’s members and embellishing them with new or modified capabilities. With inheritance, you can save time during program development by basing new classes on existing proven and debugged high-quality software. The existing class is called the superclass, and the new class is the subclass. Each subclass can become a superclass for future subclasses.

A subclass can add its own fields and methods. Therefore, a subclass is more specific than its superclass and represents a more specialized group of objects. The subclass exhibits the behaviors of its superclass and can modify those behaviors so that they operate appropriately for the subclass. This is why inheritance is sometimes referred to as specialization.

The direct superclass is the superclass from which the subclass explicitly inherits. An indirect superclass is any class above the direct superclass in the class hierarchy, which defines the inheritance relationships between classes. In Java, the class hierarchy begins with class Object (in package java.lang), which every class in Java directly or indirectly extends (or “inherits from”). Section G.5 lists the methods of class Object that are inherited by all other Java classes.

We distinguish between the is-a relationship and the has-a relationship. Is-a represents inheritance. In an is-a relationship, an object of a subclass can also be treated as an object of its superclass—e.g., a car is a vehicle. By contrast, has-a represents composition (see Appendix F). In a has-a relationship, an object contains as members references to other objects—e.g., a car has a steering wheel (and a car object has a reference to a steering-wheel object).

Later in the appendix, we discuss the concept of polymorphism, which simplifies programming with objects from the same class hierarchy. You’ll see that polymorphism also makes it possible to extend systems to add new capabilities. Finally, we discuss interfaces, which are useful for assigning common functionality to possibly unrelated classes. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to all of the interface method calls in their own customized way.

G.2 Superclasses and Subclasses

Often, an object of one class is an object of another class as well. Figure G.1 lists several examples of superclasses and subclasses—superclasses tend to be “more general” and subclasses “more specific.” For example, a CarLoan is a Loan as are HomeImprovementLoans and MortgageLoans. Thus, in Java, class CarLoan can be said to inherit from class Loan. In this context, class Loan is a superclass and class CarLoan is a subclass. A CarLoan is a specific type of Loan, but it’s incorrect to claim that every Loan is a CarLoan—the Loan could be any type of loan.

Image

Fig. G.1 | Inheritance examples.

Because every subclass object is an object of its superclass, and one superclass can have many subclasses, the set of objects represented by a superclass is often larger than the set of objects represented by any of its subclasses. For example, the superclass Vehicle represents all vehicles, including cars, trucks, boats, bicycles and so on. By contrast, subclass Car represents a smaller, more specific subset of vehicles.

University Community Member Hierarchy

Inheritance relationships form treelike hierarchical structures. A superclass exists in a hierarchical relationship with its subclasses. Let’s develop a sample class hierarchy (Fig. G.2), also called an inheritance hierarchy. A university community has thousands of members, including employees, students and alumni. Employees are either faculty or staff members.

Image

Fig. G.2 | Inheritance hierarchy for university CommunityMembers.

Faculty members are either administrators (e.g., deans and department chairpersons) or teachers. The hierarchy could contain many other classes. For example, students can be graduate or undergraduate students. Undergraduate students can be freshmen, sophomores, juniors or seniors.

Each arrow in the hierarchy represents an is-a relationship. As we follow the arrows upward in this class hierarchy, we can state, for instance, that “an Employee is a CommunityMember” and “a Teacher is a Faculty member.” CommunityMember is the direct superclass of Employee, Student and Alumnus and is an indirect superclass of all the other classes in the diagram. Starting from the bottom, you can follow the arrows and apply the is-a relationship up to the topmost superclass. For example, an Administrator is a Faculty member, is an Employee, is a CommunityMember and, of course, is an Object.

Shape Hierarchy

Now consider the Shape inheritance hierarchy in Fig. G.3. This hierarchy begins with superclass Shape, which is extended by subclasses TwoDimensionalShape and ThreeDimensionalShapeShapes are either TwoDimensionalShapes or ThreeDimensionalShapes. The third level of this hierarchy contains specific types of TwoDimensionalShapes and ThreeDimensionalShapes. As in Fig. G.2, we can follow the arrows from the bottom of the diagram to the topmost superclass in this class hierarchy to identify several is-a relationships. For instance, a Triangle is a TwoDimensionalShape and is a Shape, while a Sphere is a ThreeDimensionalShape and is a Shape. This hierarchy could contain many other classes. For example, ellipses and trapezoids are TwoDimensionalShapes.

It’s possible to treat superclass objects and subclass objects similarly—their commonalities are expressed in the superclass’s members. Objects of all classes that extend a common superclass can be treated as objects of that superclass—such objects have an is-a relationship with the superclass. Later in this appendix, we consider many examples that take advantage of the is-a relationship.

A subclass can customize methods that it inherits from its superclass. To do this, the subclass overrides (redefines) the superclass method with an appropriate implementation, as we’ll see often in this appendix’s code examples.

Image

Fig. G.3 | Inheritance hierarchy for Shapes.

G.3 protected Members

In this section, we introduce access modifier protected. Using protected access offers an intermediate level of access between public and private. A superclass’s protected members can be accessed by the class, by members of its subclasses and by members of other classes in the same package—protected members also have package access.

All public and protected superclass members retain their original access modifier when they become members of the subclass—public members of the superclass become public members of the subclass, and protected members of the superclass become protected members of the subclass. A superclass’s private members are not accessible outside the class itself. Rather, they’re hidden in its subclasses and can be accessed only through the public or protected methods inherited from the superclass.

Subclass methods can refer to public and protected members inherited from the superclass simply by using the member names. When a subclass method overrides an inherited superclass method, the superclass method can be accessed from the subclass by preceding the superclass method name with keyword super and a dot (.) separator. We discuss accessing overridden members of the superclass in Section G.4.

G.4 Relationship between Superclasses and Subclasses

We now use an inheritance hierarchy containing types of employees in a company’s payroll application to discuss the relationship between a superclass and its subclass. In this company, commission employees (who will be represented as objects of a superclass) are paid a percentage of their sales, while base-salaried commission employees (who will be represented as objects of a subclass) receive a base salary plus a percentage of their sales.

We create an example that sets the CommissionEmployee instance variables to private to enforce good software engineering. Then we show how the BasePlusCommissionEmployee subclass can use CommissionEmployee’s public methods to manipulate (in a controlled manner) the private instance variables inherited from CommissionEmployee.

G.4.1 Creating and Using a CommissionEmployee Class

We begin by declaring class CommissionEmployee (Fig. G.4). Line 4 begins the class declaration and indicates that class CommissionEmployee extends (i.e., inherits from) class Object (from package java.lang). This causes class CommissionEmployee to inherit the class Object’s methods—class Object does not have any fields. If you don’t explicitly specify which class a new class extends, the class extends Object implicitly. For this reason, you typically will not include “extends Object” in your code—we do so in this example only for demonstration purposes.

Overview of Class CommissionEmployee’s Methods and Instance Variables

Class CommissionEmployee’s public services include a constructor (lines 13–22) and methods earnings (lines 93–96) and toString (lines 99–107). Lines 25–90 declare public get and set methods for the class’s instance variables (declared in lines 6–10) firstName, lastName, socialSecurityNumber, grossSales and commissionRate. The class declares its instance variables as private, so objects of other classes cannot directly access these variables. Declaring instance variables as private and providing get and set methods to manipulate and validate them helps enforce good software engineering. Methods setGrossSales and setCommissionRate, for example, validate their arguments before assigning the values to instance variables grossSales and commissionRate, respectively. In a real-world, business-critical application, we’d also perform validation in the class’s other set methods.


 1   // Fig. G.4: CommissionEmployee.java
 2   // CommissionEmployee class represents an employee paid a
 3   // percentage of gross sales.
 4   public class CommissionEmployee extends Object
 5   {
 6      private String firstName;                              
 7      private String lastName;                               
 8      private String socialSecurityNumber;                   
 9      private double grossSales; // gross weekly sales       
10      private double commissionRate; // commission percentage
11
12      // five-argument constructor                                       
13      public CommissionEmployee( String first, String last, String ssn,  
14         double sales, double rate )                                     
15      {                                                                  
16         // implicit call to Object constructor occurs here              
17         firstName = first;                                              
18         lastName = last;                                                
19         socialSecurityNumber = ssn;                                     
20         setGrossSales( sales ); // validate and store gross sales       
21         setCommissionRate( rate ); // validate and store commission rate
22      } // end five-argument CommissionEmployee constructor              
23
24      // set first name
25      public void setFirstName( String first )
26      {
27         firstName = first; // should validate
28      } // end method setFirstName
29
30      // return first name
31      public String getFirstName()
32      {
33         return firstName;
34      } // end method getFirstName
35
36      // set last name
37      public void setLastName( String last )
38      {
39         lastName = last; // should validate
40      } // end method setLastName
41
42      // return last name
43      public String getLastName()
44      {
45         return lastName;
46      } // end method getLastName
47
48      // set social security number
49      public void setSocialSecurityNumber( String ssn )
50      {
51         socialSecurityNumber = ssn; // should validate
52      } // end method setSocialSecurityNumber
53
54      // return social security number
55      public String getSocialSecurityNumber()
56      {
57         return socialSecurityNumber;
58      } // end method getSocialSecurityNumber
59
60      // set gross sales amount
61      public void setGrossSales( double sales )
62      {
63         if ( sales >= 0.0 )
64            grossSales = sales;
65         else
66            throw new IllegalArgumentException(
67               "Gross sales must be >= 0.0" );
68      } // end method setGrossSales
69
70      // return gross sales amount
71      public double getGrossSales()
72      {
73         return grossSales;
74      } // end method getGrossSales
75
76      // set commission rate
77      public void setCommissionRate( double rate )
78      {
79         if ( rate > 0.0 && rate < 1.0 )
80            commissionRate = rate;
81         else
82            throw new IllegalArgumentException(
83               "Commission rate must be > 0.0 and < 1.0" );
84      } // end method setCommissionRate
85
86      // return commission rate
87      public double getCommissionRate()
88      {
89         return commissionRate;
90      } // end method getCommissionRate
91
92      // calculate earnings                 
93      public double earnings()              
94      {                                     
95         return commissionRate * grossSales;
96      } // end method earnings              
97
98      // return String representation of CommissionEmployee object         
99      @Override // indicates that this method overrides a superclass method
100     public String toString()                                             
101     {                                                                    
102        return String.format( "%s: %s %s %s: %s %s: %.2f %s: %.2f",    
103           "commission employee", firstName, lastName,                    
104           "social security number", socialSecurityNumber,                
105           "gross sales", grossSales,                                     
106          "commission rate", commissionRate );                            
107     } // end method toString                                             
108  } // end class CommissionEmployee


Fig. G.4 | CommissionEmployee class represents an employee paid a percentage of gross sales.

Class CommissionEmployee’s Constructor

Constructors are not inherited, so class CommissionEmployee does not inherit class Object’s constructor. However, a superclass’s constructors are still available to subclasses. In fact, the first task of any subclass constructor is to call its direct superclass’s constructor, either explicitly or implicitly (if no constructor call is specified), to ensure that the instance variables inherited from the superclass are initialized properly. In this example, class CommissionEmployee’s constructor calls class Object’s constructor implicitly. The syntax for calling a superclass constructor explicitly is discussed in Section G.4.3. If the code does not include an explicit call to the superclass constructor, Java implicitly calls the superclass’s default or no-argument constructor. The comment in line 16 of Fig. G.4 indicates where the implicit call to the superclass Object’s default constructor is made (you do not write the code for this call). Object’s default (empty) constructor does nothing. Even if a class does not have constructors, the default constructor that the compiler implicitly declares for the class will call the superclass’s default or no-argument constructor.

After the implicit call to Object’s constructor, lines 17–21 of CommissionEmployee’s constructor assign values to the class’s instance variables. We do not validate the values of arguments first, last and ssn before assigning them to the corresponding instance variables. We could validate the first and last names—perhaps to ensure that they’re of a reasonable length. Similarly, a social security number could be validated using regular expressions to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789 or 123456789).

Class CommissionEmployee’s earnings Method

Method earnings (lines 93–96) calculates a CommissionEmployee’s earnings. Line 95 multiplies the commissionRate by the grossSales and returns the result.

Class CommissionEmployee’s toString Method and the @Override Annotation

Method toString (lines 99–107) is special—it’s one of the methods that every class inherits directly or indirectly from class Object (summarized in Section G.5). Method toString returns a String representing an object. It’s called implicitly whenever an object must be converted to a String representation, such as when an object is output by printf or output by String method format via the %s format specifier. Class Object’s toString method returns a String that includes the name of the object’s class. It’s primarily a placeholder that can be overridden by a subclass to specify an appropriate String representation of the data in a subclass object. Method toString of class CommissionEmployee overrides (redefines) class Object’s toString method. When invoked, CommissionEmployee’s toString method uses String method format to return a String containing information about the CommissionEmployee. To override a superclass method, a subclass must declare a method with the same signature (method name, number of parameters, parameter types and order of parameter types) as the superclass method—Object’s toString method takes no parameters, so CommissionEmployee declares toString with no parameters.

Line 99 uses the @Override annotation to indicate that method toString should override a superclass method. Annotations have several purposes. For example, when you attempt to override a superclass method, common errors include naming the subclass method incorrectly, or using the wrong number or types of parameters in the parameter list. Each of these problems creates an unintentional overload of the superclass method. If you then attempt to call the method on a subclass object, the superclass’s version is invoked and the subclass version is ignored—potentially leading to subtle logic errors. When the compiler encounters a method declared with @Override, it compares the method’s signature with the superclass’s method signatures. If there isn’t an exact match, the compiler issues an error message, such as “method does not override or implement a method from a supertype.” This indicates that you’ve accidentally overloaded a superclass method. You can then fix your method’s signature so that it matches one in the superclass.

In web applications and web services, annotations can also add complex support code to your classes to simplify the development process and can be used by servers to configure certain aspects of web applications.


Image Common Programming Error G.1

It’s a syntax error to override a method with a more restricted access modifier—a public method of the superclass cannot become a protected or private method in the subclass; a protected method of the superclass cannot become a private method in the subclass. Doing so would break the is-a relationship in which it’s required that all subclass objects be able to respond to method calls that are made to public methods declared in the superclass. If a public method, for example, could be overridden as a protected or private method, the subclass objects would not be able to respond to the same method calls as superclass objects. Once a method is declared public in a superclass, the method remains public for all that class’s direct and indirect subclasses.


Class CommissionEmployeeTest

Figure G.5 tests class CommissionEmployee. Lines 9–10 instantiate a CommissionEmployee object and invoke CommissionEmployee’s constructor (lines 13–22 of Fig. G.4) to initialize it with "Sue" as the first name, "Jones" as the last name, "222-22-2222" as the social security number, 10000 as the gross sales amount and .06 as the commission rate. Lines 15–24 use CommissionEmployee’s get methods to retrieve the object’s instance-variable values for output. Lines 26–27 invoke the object’s methods setGrossSales and setCommissionRate to change the values of instance variables grossSales and commissionRate. Lines 29–30 output the String representation of the updated CommissionEmployee. When an object is output using the %s format specifier, the object’s toString method is invoked implicitly to obtain the object’s String representation. [Note: Early in this appendix, we do not use the earnings methods of our classes—they’re used extensively in the polymorphism part of the appendix.]


 1   // Fig. G.5: CommissionEmployeeTest.java
 2   // CommissionEmployee class test program.
 3
 4   public class CommissionEmployeeTest
 5   {
 6      public static void main( String[] args )
 7      {
 8         // instantiate CommissionEmployee object
 9         CommissionEmployee employee = new CommissionEmployee(
10            "Sue", "Jones", "222-22-2222", 10000, .06 );      
11
12         // get commission employee data
13         System.out.println(
14            "Employee information obtained by get methods: " );
15         System.out.printf( "%s %s ", "First name is",
16            employee.getFirstName() );
17         System.out.printf( "%s %s ", "Last name is",
18            employee.getLastName() );
19         System.out.printf( "%s %s ", "Social security number is",
20            employee.getSocialSecurityNumber() );
21         System.out.printf( "%s %.2f ", "Gross sales is",
22            employee.getGrossSales() );
23         System.out.printf( "%s %.2f ", "Commission rate is",
24            employee.getCommissionRate() );
25
26         employee.setGrossSales( 500 ); // set gross sales       
27         employee.setCommissionRate( .1 ); // set commission rate
28
29         System.out.printf( " %s: %s ",                                
30            "Updated employee information obtained by toString", employee );
31      } // end main
32   } // end class CommissionEmployeeTest


Employee information obtained by get methods:

First name is Sue
Last name is Jones
Social security number is 222-22-2222
Gross sales is 10000.00
Commission rate is 0.06

Updated employee information obtained by toString:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 500.00
commission rate: 0.10


Fig. G.5 | CommissionEmployee class test program.

G.4.2 Creating and Using a BasePlusCommissionEmployee Class

We now discuss the second part of our introduction to inheritance by declaring and testing (a completely new and independent) class BasePlusCommissionEmployee (Fig. G.6), which contains a first name, last name, social security number, gross sales amount, commission rate and base salary. Class BasePlusCommissionEmployee’s public services include a BasePlusCommissionEmployee constructor (lines 15–25) and methods earnings (lines 112–115) and toString (lines 118–127). Lines 28–109 declare public get and set methods for the class’s private instance variables (declared in lines 7–12) firstName, lastName, socialSecurityNumber, grossSales, commissionRate and baseSalary. These variables and methods encapsulate all the necessary features of a base-salaried commission employee. Note the similarity between this class and class CommissionEmployee (Fig. G.4)—in this example, we’ll not yet exploit that similarity.


 1   // Fig. G.6: BasePlusCommissionEmployee.java
 2   // BasePlusCommissionEmployee class represents an employee who receives
 3   // a base salary in addition to a commission.
 4
 5   public class BasePlusCommissionEmployee
 6   {
 7      private String firstName;
 8      private String lastName;
 9      private String socialSecurityNumber;
10      private double grossSales; // gross weekly sales
11      private double commissionRate; // commission percentage
12      private double baseSalary; // base salary per week
13
14      // six-argument constructor
15      public BasePlusCommissionEmployee( String first, String last,
16         String ssn, double sales, double rate, double salary )
17      {
18         // implicit call to Object constructor occurs here
19         firstName = first;
20         lastName = last;
21         socialSecurityNumber = ssn;
22         setGrossSales( sales ); // validate and store gross sales
23         setCommissionRate( rate ); // validate and store commission rate
24         setBaseSalary( salary ); // validate and store base salary
25      } // end six-argument BasePlusCommissionEmployee constructor
26
27      // set first name
28      public void setFirstName( String first )
29      {
30         firstName = first; // should validate
31      } // end method setFirstName
32
33      // return first name
34      public String getFirstName()
35      {
36         return firstName;
37      } // end method getFirstName
38
39      // set last name
40      public void setLastName( String last )
41      {
42         lastName = last; // should validate
43      } // end method setLastName
44
45      // return last name
46      public String getLastName()
47      {
48         return lastName;
49      } // end method getLastName
50
51      // set social security number
52      public void setSocialSecurityNumber( String ssn )
53      {
54         socialSecurityNumber = ssn; // should validate
55      } // end method setSocialSecurityNumber
56
57      // return social security number
58      public String getSocialSecurityNumber()
59      {
60         return socialSecurityNumber;
61      } // end method getSocialSecurityNumber
62
63      // set gross sales amount
64      public void setGrossSales( double sales )
65      {
66         if ( sales >= 0.0 )
67            grossSales = sales;
68         else
69            throw new IllegalArgumentException(
70               "Gross sales must be >= 0.0" );
71      } // end method setGrossSales
72
73      // return gross sales amount
74      public double getGrossSales()
75      {
76         return grossSales;
77      } // end method getGrossSales
78
79      // set commission rate
80      public void setCommissionRate( double rate )
81      {
82         if ( rate > 0.0 && rate < 1.0 )
83            commissionRate = rate;
84         else
85            throw new IllegalArgumentException(
86               "Commission rate must be > 0.0 and < 1.0" );
87      } // end method setCommissionRate
88
89      // return commission rate
90      public double getCommissionRate()
91      {
92         return commissionRate;
93      } // end method getCommissionRate
94
95      // set base salary
96      public void setBaseSalary( double salary )
97      {
98         if ( salary >= 0.0 )
99            baseSalary = salary;
100        else
101           throw new IllegalArgumentException(
102              "Base salary must be >= 0.0" );
103     } // end method setBaseSalary
104
105     // return base salary                                    
106     public double getBaseSalary()                            
107     {                                                        
108        return baseSalary;                                    
109     } // end method getBaseSalary                            
110
111     // calculate earnings
112     public double earnings()
113     {
114        return baseSalary + ( commissionRate * grossSales );
115     } // end method earnings
116
117     // return String representation of BasePlusCommissionEmployee
118     @Override // indicates that this method overrides a superclass method
119     public String toString()
120     {
121        return String.format(
122           "%s: %s %s %s: %s %s: %.2f %s: %.2f %s: %.2f",
123           "base-salaried commission employee", firstName, lastName,
124           "social security number", socialSecurityNumber,
125           "gross sales", grossSales, "commission rate", commissionRate,
126           "base salary", baseSalary );
127     } // end method toString
128  } // end class BasePlusCommissionEmployee


Fig. G.6 | BasePlusCommissionEmployee class represents an employee who receives a base salary in addition to a commission.

Class BasePlusCommissionEmployee does not specify “extends Object” in line 5, so the class implicitly extends Object. Also, like class CommissionEmployee’s constructor (lines 13–22 of Fig. G.4), class BasePlusCommissionEmployee’s constructor invokes class Object’s default constructor implicitly, as noted in the comment in line 18.

Class BasePlusCommissionEmployee’s earnings method (lines 112–115) returns the result of adding the BasePlusCommissionEmployee’s base salary to the product of the commission rate and the employee’s gross sales.

Class BasePlusCommissionEmployee overrides Object method toString to return a String containing the BasePlusCommissionEmployee’s information. Once again, we use format specifier %.2f to format the gross sales, commission rate and base salary with two digits of precision to the right of the decimal point (line 122).

Testing Class BasePlusCommissionEmployee

Figure G.7 tests class BasePlusCommissionEmployee. Lines 9–11 create a BasePlusCommissionEmployee object and pass "Bob", "Lewis", "333-33-3333", 5000, .04 and 300 to the constructor as the first name, last name, social security number, gross sales, commission rate and base salary, respectively. Lines 16–27 use BasePlusCommissionEmployee’s get methods to retrieve the values of the object’s instance variables for output. Line 29 invokes the object’s setBaseSalary method to change the base salary. Method setBaseSalary (Fig. G.6, lines 96–103) ensures that instance variable baseSalary is not assigned a negative value. Lines 31–33 of Fig. G.7 invoke method toString explicitly to get the object’s String representation.

Notes on Class BasePlusCommissionEmployee

Much of class BasePlusCommissionEmployee’s code (Fig. G.6) is similar, or identical, to that of class CommissionEmployee (Fig. G.4). For example, private instance variables firstName and lastName and methods setFirstName, getFirstName, setLastName and getLastName are identical to those of class CommissionEmployee. The classes also both contain private instance variables socialSecurityNumber, commissionRate and grossSales, and corresponding get and set methods. In addition, the BasePlusCommissionEmployee constructor is almost identical to that of class CommissionEmployee, except that BasePlusCommissionEmployee’s constructor also sets the baseSalary. The other additions to class BasePlusCommissionEmployee are private instance variable baseSalary and methods setBaseSalary and getBaseSalary. Class BasePlusCommissionEmployee’s toString method is nearly identical to that of class CommissionEmployee except that it also outputs instance variable baseSalary with two digits of precision to the right of the decimal point.


 1   // Fig. G.7: BasePlusCommissionEmployeeTest.java
 2   // BasePlusCommissionEmployee test program.
 3
 4   public class BasePlusCommissionEmployeeTest
 5   {
 6      public static void main( String[] args )
 7      {
 8         // instantiate BasePlusCommissionEmployee object
 9         BasePlusCommissionEmployee employee =              
10            new BasePlusCommissionEmployee(                 
11            "Bob", "Lewis", "333-33-3333", 5000, .04, 300 );
12
13         // get base-salaried commission employee data
14         System.out.println(
15            "Employee information obtained by get methods: " );
16         System.out.printf( "%s %s ", "First name is",
17            employee.getFirstName() );
18         System.out.printf( "%s %s ", "Last name is",
19            employee.getLastName() );
20         System.out.printf( "%s %s ", "Social security number is",
21            employee.getSocialSecurityNumber() );
22         System.out.printf( "%s %.2f ", "Gross sales is",
23            employee.getGrossSales() );
24         System.out.printf( "%s %.2f ", "Commission rate is",
25            employee.getCommissionRate() );
26         System.out.printf( "%s %.2f ", "Base salary is",
27            employee.getBaseSalary() );
28
29         employee.setBaseSalary( 1000 ); // set base salary
30
31         System.out.printf( " %s: %s ",
32            "Updated employee information obtained by toString",
33            employee.toString() );
34      } // end main
35   } // end class BasePlusCommissionEmployeeTest


Employee information obtained by get methods:

First name is Bob
Last name is Lewis
Social security number is 333-33-3333
Gross sales is 5000.00
Commission rate is 0.04
Base salary is 300.00

Updated employee information obtained by toString:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 1000.00


Fig. G.7 | BasePlusCommissionEmployee test program.

We literally copied code from class CommissionEmployee and pasted it into class BasePlusCommissionEmployee, then modified class BasePlusCommissionEmployee to include a base salary and methods that manipulate the base salary. This “copy-and-paste” approach is often error prone and time consuming. Worse yet, it spreads copies of the same code throughout a system, creating a code-maintenance nightmare. Is there a way to “absorb” the instance variables and methods of one class in a way that makes them part of other classes without duplicating code? Next we answer this question, using a more elegant approach to building classes that emphasizes the benefits of inheritance.


Image Software Engineering Observation G.1

With inheritance, the common instance variables and methods of all the classes in the hierarchy are declared in a superclass. When changes are made for these common features in the superclass—subclasses then inherit the changes. Without inheritance, changes would need to be made to all the source-code files that contain a copy of the code in question.


G.4.3 Creating a CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy

Now we redeclare class BasePlusCommissionEmployee (Fig. G.8) to extend class CommissionEmployee (Fig. G.4). A BasePlusCommissionEmployee object is a CommissionEmployee, because inheritance passes on class CommissionEmployee’s capabilities. Class BasePlusCommissionEmployee also has instance variable baseSalary (Fig. G.8, line 6). Keyword extends (line 4) indicates inheritance. BasePlusCommissionEmployee inherits CommissionEmployee’s instance variables and methods, but only the superclass’s public and protected members are directly accessible in the subclass. The CommissionEmployee constructor is not inherited. So, the public BasePlusCommissionEmployee services include its constructor (lines 9–16), public methods inherited from CommissionEmployee, and methods setBaseSalary (lines 19–26), getBaseSalary (lines 29–32), earnings (lines 35–40) and toString (lines 43–53). Methods earnings and toString override the corresponding methods in class CommissionEmployee because their superclass versions do not properly calculate a BasePlusCommissionEmployee’s earnings or return an appropriate String representation.


 1   // Fig. G.8: BasePlusCommissionEmployee.java
 2   // private superclass members cannot be accessed in a subclass.
 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         // explicit call to superclass CommissionEmployee constructor
13         super( first, last, ssn, sales, rate );                      
14
15         setBaseSalary( salary ); // validate and store base salary
16      } // end six-argument BasePlusCommissionEmployee constructor
17
18      // set base salary
19      public void setBaseSalary( double salary )
20      {
21         if ( salary >= 0.0 )
22            baseSalary = salary;
23         else
24            throw new IllegalArgumentException(
25               "Base salary must be >= 0.0" );
26      } // end method setBaseSalary
27
28      // return base salary
29      public double getBaseSalary()
30      {
31         return baseSalary;
32      } // end method getBaseSalary
33
34      // calculate earnings
35      @Override // indicates that this method overrides a superclass method
36      public double earnings()
37      {
38         // not allowed: commissionRate and grossSales private in superclass
39         return baseSalary + ( commissionRate * grossSales );               
40      } // end method earnings
41
42      // return String representation of BasePlusCommissionEmployee
43      @Override // indicates that this method overrides a superclass method
44      public String toString()
45      {
46         // not allowed: attempts to access private superclass members   
47         return String.format(                                           
48            "%s: %s %s %s: %s %s: %.2f %s: %.2f %s: %.2f",           
49            "base-salaried commission employee", firstName, lastName,    
50            "social security number", socialSecurityNumber,              
51            "gross sales", grossSales, "commission rate", commissionRate,
52            "base salary", baseSalary );                                 
53      } // end method toString
54   } // end class BasePlusCommissionEmployee


BasePlusCommissionEmployee.java:39: commissionRate has private access in
CommissionEmployee
      return baseSalary + ( commissionRate * grossSales );
                            ^
BasePlusCommissionEmployee.java:39: grossSales has private access in
CommissionEmployee
      return baseSalary + ( commissionRate * grossSales );
                                             ^
BasePlusCommissionEmployee.java:49: firstName has private access in
CommissionEmployee
         "base-salaried commission employee", firstName, lastName,
                                              ^
BasePlusCommissionEmployee.java:49: lastName has private access in
CommissionEmployee
         "base-salaried commission employee", firstName, lastName,
                                                         ^
BasePlusCommissionEmployee.java:50: socialSecurityNumber has private access
in CommissionEmployee
         "social security number", socialSecurityNumber,
                                   ^
BasePlusCommissionEmployee.java:51: grossSales has private access in
CommissionEmployee
         "gross sales", grossSales, "commission rate", commissionRate,
                        ^
BasePlusCommissionEmployee.java:51: commissionRate has private access in
CommissionEmployee
         "gross sales", grossSales, "commission rate", commissionRate,
                                                       ^
7 errors


Fig. G.8 | private superclass members cannot be accessed in a subclass.

A Subclass’s Constructor Must Call Its Superclass’s Constructor

Each subclass constructor must implicitly or explicitly call its superclass constructor to initialize the instance variables inherited from the superclass. Line 13 in BasePlusCommissionEmployee’s six-argument constructor (lines 9–16) explicitly calls class CommissionEmployee’s five-argument constructor (declared at lines 13–22 of Fig. G.4) to initialize the superclass portion of a BasePlusCommissionEmployee object (i.e., variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate). We do this by using the superclass constructor call syntax—keyword super, followed by a set of parentheses containing the superclass constructor arguments. The arguments first, last, ssn, sales and rate are used to initialize superclass members firstName, lastName, socialSecurityNumber, grossSales and commissionRate, respectively. If BasePlusCommissionEmployee’s constructor did not invoke the superclass’s constructor explicitly, Java would attempt to invoke the superclass’s no-argument or default constructor. Class CommissionEmployee does not have such a constructor, so the compiler would issue an error. The explicit superclass constructor call in line 13 of Fig. G.8 must be the first statement in the subclass constructor’s body. When a superclass contains a no-argument constructor, you can use super() to call that constructor explicitly, but this is rarely done.

BasePlusCommissionEmployee Method Earnings

The compiler generates errors for line 39 because superclass CommissionEmployee’s instance variables commissionRate and grossSales are private—subclass BasePlusCommissionEmployee’s methods are not allowed to access superclass CommissionEmployee’s private instance variables. We highlighted the erroneous code. The compiler issues additional errors at lines 49–51 of BasePlusCommissionEmployee’s toString method for the same reason. The errors in BasePlusCommissionEmployee could have been prevented by using the get methods inherited from class CommissionEmployee. For example, line 39 could have used getCommissionRate and getGrossSales to access CommissionEmployee’s private instance variables commissionRate and grossSales, respectively. Lines 49–51 also could have used appropriate get methods to retrieve the values of the superclass’s instance variables.

G.4.4 CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy Using protected Instance Variables

To enable class BasePlusCommissionEmployee to directly access superclass instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate, we can declare those members as protected in the superclass. As we discussed in Section G.3, a superclass’s protected members are accessible by all subclasses of that superclass. In the new CommissionEmployee class, we modified only lines 6–10 of Fig. G.4 to declare the instance variables with the protected access modifier as follows:

protected String firstName;
protected String lastName;
protected String socialSecurityNumber;
protected double grossSales; // gross weekly sales
protected double commissionRate; // commission percentage

The rest of the class declaration (which is not shown here) is identical to that of Fig. G.4.

We could have declared CommissionEmployee’s instance variables public to enable subclass BasePlusCommissionEmployee to access them. However, declaring public instance variables is poor software engineering because it allows unrestricted access to the these variables, greatly increasing the chance of errors. With protected instance variables, the subclass gets access to the instance variables, but classes that are not subclasses and classes that are not in the same package cannot access these variables directly—recall that protected class members are also visible to other classes in the same package.

Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployee (Fig. G.9) extends the new version of class CommissionEmployee with protected instance variables. BasePlusCommissionEmployee objects inherit CommissionEmployee’s protected instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate—all these variables are now protected members of BasePlusCommissionEmployee. As a result, the compiler does not generate errors when compiling line 37 of method earnings and lines 46–48 of method toString. If another class extends this version of class BasePlusCommissionEmployee, the new subclass also can access the protected members.


 1   // Fig. G.9: BasePlusCommissionEmployee.java
 2   // BasePlusCommissionEmployee inherits protected instance
 3   // variables from CommissionEmployee.
 4
 5   public class BasePlusCommissionEmployee extends CommissionEmployee
 6   {
 7      private double baseSalary; // base salary per week
 8
 9      // six-argument constructor
10      public BasePlusCommissionEmployee( String first, String last,
11         String ssn, double sales, double rate, double salary )
12      {
13         super( first, last, ssn, sales, rate );
14         setBaseSalary( salary ); // validate and store base salary
15      } // end six-argument BasePlusCommissionEmployee constructor
16
17      // set base salary
18      public void setBaseSalary( double salary )
19      {
20         if ( salary >= 0.0 )
21            baseSalary = salary;
22         else
23            throw new IllegalArgumentException(
24               "Base salary must be >= 0.0" );
25      } // end method setBaseSalary
26
27      // return base salary
28      public double getBaseSalary()
29      {
30         return baseSalary;
31      } // end method getBaseSalary
32
33      // calculate earnings
34      @Override // indicates that this method overrides a superclass method
35      public double earnings()
36      {
37         return baseSalary + ( commissionRate * grossSales );
38      } // end method earnings
39
40      // return String representation of BasePlusCommissionEmployee
41      @Override // indicates that this method overrides a superclass method
42      public String toString()
43      {
44         return String.format(                                           
45            "%s: %s %s %s: %s %s: %.2f %s: %.2f %s: %.2f",           
46            "base-salaried commission employee", firstName, lastName,    
47            "social security number", socialSecurityNumber,              
48            "gross sales", grossSales, "commission rate", commissionRate,
49            "base salary", baseSalary );
50      } // end method toString
51   } // end class BasePlusCommissionEmployee


Fig. G.9 | BasePlusCommissionEmployee inherits protected instance variables from CommissionEmployee.

When you create a BasePlusCommissionEmployee object, it contains all instance variables declared in the class hierarchy to that point—i.e., those from classes Object, CommissionEmployee and BasePlusCommissionEmployee. Class BasePlusCommissionEmployee does not inherit class CommissionEmployee’s constructor. However, class BasePlusCommissionEmployee’s six-argument constructor (lines 10–15) calls class CommissionEmployee’s five-argument constructor explicitly to initialize the instance variables that BasePlusCommissionEmployee inherited from class CommissionEmployee. Similarly, class CommissionEmployee’s constructor implicitly calls class Object’s constructor. BasePlusCommissionEmployee’s constructor must do this explicitly because CommissionEmployee does not provide a no-argument constructor that could be invoked implicitly.

Testing Class BasePlusCommissionEmployee

The BasePlusCommissionEmployeeTest class for this example is identical to that of Fig. G.7 and produces the same output, so we do not show it here. Although the version of class BasePlusCommissionEmployee in Fig. G.6 does not use inheritance and the version in Fig. G.9 does, both classes provide the same functionality. The source code in Fig. G.9 (51 lines) is considerably shorter than that in Fig. G.6 (128 lines), because most of BasePlusCommissionEmployee’s functionality is now inherited from CommissionEmployee—there’s now only one copy of the CommissionEmployee functionality. This makes the code easier to maintain, modify and debug, because the code related to a commission employee exists only in class CommissionEmployee.

Notes on Using protected Instance Variables

In this example, we declared superclass instance variables as protected so that subclasses could access them. Inheriting protected instance variables slightly increases performance, because we can directly access the variables in the subclass without incurring the overhead of a set or get method call. In most cases, however, it’s better to use private instance variables to encourage proper software engineering, and leave code optimization issues to the compiler. Your code will be easier to maintain, modify and debug.

Using protected instance variables creates several potential problems. First, the subclass object can set an inherited variable’s value directly without using a set method. Therefore, a subclass object can assign an invalid value to the variable, possibly leaving the object in an inconsistent state. For example, if we were to declare CommissionEmployee’s instance variable grossSales as protected, a subclass object (e.g., BasePlusCommissionEmployee) could then assign a negative value to grossSales. Another problem with using protected instance variables is that subclass methods are more likely to be written so that they depend on the superclass’s data implementation. In practice, subclasses should depend only on the superclass services (i.e., non-private methods) and not on the superclass data implementation. With protected instance variables in the superclass, we may need to modify all the subclasses of the superclass if the superclass implementation changes. For example, if for some reason we were to change the names of instance variables firstName and lastName to first and last, then we would have to do so for all occurrences in which a subclass directly references superclass instance variables firstName and lastName. In such a case, the software is said to be fragile or brittle, because a small change in the superclass can “break” subclass implementation. You should be able to change the superclass implementation while still providing the same services to the subclasses. Of course, if the superclass services change, we must reimplement our subclasses. A third problem is that a class’s protected members are visible to all classes in the same package as the class containing the protected members—this is not always desirable.


Image Software Engineering Observation G.2

Use the protected access modifier when a superclass should provide a method only to its subclasses and other classes in the same package, but not to other clients.


 


Image Software Engineering Observation G.3

Declaring superclass instance variables private (as opposed to protected) enables the superclass implementation of these instance variables to change without affecting subclass implementations.


 


Image Error-Prevention Tip G.1

When possible, do not include protected instance variables in a superclass. Instead, include non-private methods that access private instance variables. This will help ensure that objects of the class maintain consistent states.


G.4.5 CommissionEmployee–BasePlusCommissionEmployee Inheritance Hierarchy Using private Instance Variables

Let’s reexamine our hierarchy once more, this time using good software engineering practices. Class CommissionEmployee (Fig. G.10) declares instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate as private (lines 6–10) and provides public methods setFirstName, getFirstName, setLastName, getLastName, setSocialSecurityNumber, getSocialSecurityNumber, setGrossSales, getGrossSales, setCommissionRate, getCommissionRate, earnings and toString for manipulating these values. Methods earnings (lines 93–96) and toString (lines 99–107) use the class’s get methods to obtain the values of its instance variables. If we decide to change the instance-variable names, the earnings and toString declarations will not require modification—only the bodies of the get and set methods that directly manipulate the instance variables will need to change. These changes occur solely within the superclass—no changes to the subclass are needed. Localizing the effects of changes like this is a good software engineering practice.


 1   // Fig. G.10: CommissionEmployee.java
 2   // CommissionEmployee class uses methods to manipulate its
 3   // private instance variables.
 4   public class CommissionEmployee
 5   {
 6      private String firstName;                              
 7      private String lastName;                               
 8      private String socialSecurityNumber;                   
 9      private double grossSales; // gross weekly sales       
10      private double commissionRate; // commission percentage
11
12      // five-argument constructor
13      public CommissionEmployee( String first, String last, String ssn,
14         double sales, double rate )
15      {
16         // implicit call to Object constructor occurs here
17         firstName = first;
18         lastName = last;
19         socialSecurityNumber = ssn;
20         setGrossSales( sales ); // validate and store gross sales
21         setCommissionRate( rate ); // validate and store commission rate
22      } // end five-argument CommissionEmployee constructor
23
24      // set first name
25      public void setFirstName( String first )
26      {
27         firstName = first; // should validate
28      } // end method setFirstName
29
30      // return first name
31      public String getFirstName()
32      {
33         return firstName;
34      } // end method getFirstName
35
36      // set last name
37      public void setLastName( String last )
38      {
39         la5stName = last; // should validate
40      } // end method setLastName
41
42      // return last name
43      public String getLastName()
44      {
45         return lastName;
46      } // end method getLastName
47
48      // set social security number
49      public void setSocialSecurityNumber( String ssn )
50      {
51         socialSecurityNumber = ssn; // should validate
52      } // end method setSocialSecurityNumber
53
54      // return social security number
55      public String getSocialSecurityNumber()
56      {
57         return socialSecurityNumber;
58      } // end method getSocialSecurityNumber
59
60      // set gross sales amount
61      public void setGrossSales( double sales )
62      {
63         if ( sales >= 0.0 )
64            grossSales = sales;
65         else
66            throw new IllegalArgumentException(
67               "Gross sales must be >= 0.0" );
68      } // end method setGrossSales
69
70      // return gross sales amount
71      public double getGrossSales()
72      {
73         return grossSales;
74      } // end method getGrossSales
75
76      // set commission rate
77      public void setCommissionRate( double rate )
78      {
79         if ( rate > 0.0 && rate < 1.0 )
80            commissionRate = rate;
81         else
82            throw new IllegalArgumentException(
83               "Commission rate must be > 0.0 and < 1.0" );
84      } // end method setCommissionRate
85
86      // return commission rate
87      public double getCommissionRate()
88      {
89         return commissionRate;
90      } // end method getCommissionRate
91
92      // calculate earnings
93      public double earnings()
94      {
95         return getCommissionRate() * getGrossSales();
96      } // end method earnings
97
98      // return String representation of CommissionEmployee object
99      @Override // indicates that this method overrides a superclass method
100     public String toString()
101     {
102        return String.format( "%s: %s %s %s: %s %s: %.2f %s: %.2f",
103           "commission employee", getFirstName(), getLastName(),
104           "social security number", getSocialSecurityNumber(),
105           "gross sales", getGrossSales(),
106           "commission rate", getCommissionRate() );
107     } // end method toString
108  } // end class CommissionEmployee


Fig. G.10 | CommissionEmployee class uses methods to manipulate its private instance variables.

Subclass BasePlusCommissionEmployee (Fig. G.11) inherits CommissionEmployee’s non-private methods and can access the private superclass members via those methods. Class BasePlusCommissionEmployee has several changes that distinguish it from Fig. G.9. Methods earnings (lines 35–39) and toString (lines 42–47) each invoke method getBaseSalary to obtain the base salary value, rather than accessing baseSalary directly. If we decide to rename instance variable baseSalary, only the bodies of method setBaseSalary and getBaseSalary will need to change.


 1   // Fig. G.11: BasePlusCommissionEmployee.java
 2   // BasePlusCommissionEmployee class inherits from CommissionEmployee
 3   // and accesses the superclass's private data via inherited
 4   // public methods.
 5
 6   public class BasePlusCommissionEmployee extends CommissionEmployee
 7   {
 8      private double baseSalary; // base salary per week
 9
10      // six-argument constructor
11      public BasePlusCommissionEmployee( String first, String last,
12         String ssn, double sales, double rate, double salary )
13      {
14         super( first, last, ssn, sales, rate );
15         setBaseSalary( salary ); // validate and store base salary
16      } // end six-argument BasePlusCommissionEmployee constructor
17
18      // set base salary
19      public void setBaseSalary( double salary )
20      {
21         if ( salary >= 0.0 )
22            baseSalary = salary;
23         else
24            throw new IllegalArgumentException(
25               "Base salary must be >= 0.0" );
26      } // end method setBaseSalary
27
28      // return base salary
29      public double getBaseSalary()
30      {
31         return baseSalary;
32      } // end method getBaseSalary
33
34      // calculate earnings
35      @Override // indicates that this method overrides a superclass method
36      public double earnings()
37      {
38         return getBaseSalary() + super.earnings();
39      } // end method earnings
40
41      // return String representation of BasePlusCommissionEmployee
42      @Override // indicates that this method overrides a superclass method
43      public String toString()
44      {
45         return String.format( "%s %s %s: %.2f", "base-salaried",
46            super.toString(), "base salary", getBaseSalary() );   
47      } // end method toString
48   } // end class BasePlusCommissionEmployee


Fig. G.11 | BasePlusCommissionEmployee class inherits from CommissionEmployee and accesses the superclass’s private data via inherited public methods.

Class BasePlusCommissionEmployee’s earnings Method

Method earnings (lines 35–39) overrides class CommissionEmployee’s earnings method (Fig. G.10, lines 93–96) to calculate a base-salaried commission employee’s earnings. The new version obtains the portion of the earnings based on commission alone by calling CommissionEmployee’s earnings method with super.earnings() (line 38), then adds the base salary to this value to calculate the total earnings. Note the syntax used to invoke an overridden superclass method from a subclass—place the keyword super and a dot (.) separator before the superclass method name. This method invocation is a good software engineering practice—if a method performs all or some of the actions needed by another method, call that method rather than duplicate its code. By having BasePlusCommissionEmployee’s earnings method invoke CommissionEmployee’s earnings method to calculate part of a BasePlusCommissionEmployee object’s earnings, we avoid duplicating the code and reduce code-maintenance problems. If we did not use “super.” then BasePlusCommissionEmployee’s earnings method would call itself rather than the superclass version. This would result in a phenomenon called infinite recursion, which would eventually cause the method-call stack to overflow—a fatal runtime error.

Class BasePlusCommissionEmployee’s toString Method

Similarly, BasePlusCommissionEmployee’s toString method (Fig. G.11, lines 42–47) overrides class CommissionEmployee’s toString method (Fig. G.10, lines 99–107) to return a String representation that’s appropriate for a base-salaried commission employee. The new version creates part of a BasePlusCommissionEmployee object’s String representation (i.e., the String "commission employee" and the values of class CommissionEmployee’s private instance variables) by calling CommissionEmployee’s toString method with the expression super.toString() (Fig. G.11, line 46). BasePlusCommissionEmployee’s toString method then outputs the remainder of a BasePlusCommissionEmployee object’s String representation (i.e., the value of class BasePlusCommissionEmployee’s base salary).


Image Common Programming Error G.2

When a superclass method is overridden in a subclass, the subclass version often calls the superclass version to do a portion of the work. Failure to prefix the superclass method name with the keyword super and a dot (.) separator when calling the superclass’s method causes the subclass method to call itself, potentially creating an error called infinite recursion. Recursion, used correctly, is a powerful capability.


Testing Class BasePlusCommissionEmployee

Class BasePlusCommissionEmployeeTest performs the same manipulations on a BasePlusCommissionEmployee object as in Fig. G.7 and produces the same output, so we do not show it here. Although each BasePlusCommissionEmployee class you’ve seen behaves identically, the version in Fig. G.11 is the best engineered. By using inheritance and by calling methods that hide the data and ensure consistency, we’ve efficiently and effectively constructed a well-engineered class.

G.5 Class Object

As we discussed earlier in this appendix, all classes in Java inherit directly or indirectly from the Object class (package java.lang), so its 11 methods (some are overloaded) are inherited by all other classes. Figure G.12 summarizes Object’s methods. We discuss several Object methods throughout this book (as indicated in Fig. G.12).

Image
Image

Fig. G.12 | Object methods.

Recall from Appendix E that arrays are objects. As a result, like all other objects, arrays inherit the members of class Object. Every array has an overridden clone method that copies the array. However, if the array stores references to objects, the objects are not copied—a shallow copy is performed.

G.6 Introduction to Polymorphism

We continue our study of object-oriented programming by explaining and demonstrating polymorphism with inheritance hierarchies. Polymorphism enables you to “program in the general” rather than “program in the specific.” In particular, polymorphism enables you to write programs that process objects that share the same superclass (either directly or indirectly) as if they’re all objects of the superclass; this can simplify programming.

Consider the following example of polymorphism. Suppose we create a program that simulates the movement of several types of animals for a biological study. Classes Fish, Frog and Bird represent the types of animals under investigation. Imagine that each class extends superclass Animal, which contains a method move and maintains an animal’s current location as x-y coordinates. Each subclass implements method move. Our program maintains an Animal array containing references to objects of the various Animal subclasses. To simulate the animals’ movements, the program sends each object the same message once per second—namely, move. Each specific type of Animal responds to a move message in its own way—a Fish might swim three feet, a Frog might jump five feet and a Bird might fly ten feet. Each object knows how to modify its x-y coordinates appropriately for its specific type of movement. Relying on each object to know how to “do the right thing” (i.e., do what is appropriate for that type of object) in response to the same method call is the key concept of polymorphism. The same message (in this case, move) sent to a variety of objects has “many forms” of results—hence the term polymorphism.

Programming in the Specific

Occasionally, when performing polymorphic processing, we need to program “in the specific.” We’ll demonstrate that a program can determine the type of an object at execution time and act on that object accordingly.

Interfaces

The appendix continues with an introduction to Java interfaces. An interface describes a set of methods that can be called on an object, but does not provide concrete implementations for all the methods. You can declare classes that implement (i.e., provide concrete implementations for the methods of) one or more interfaces. Each interface method must be declared in all the classes that explicitly implement the interface. Once a class implements an interface, all objects of that class have an is-a relationship with the interface type, and all objects of the class are guaranteed to provide the functionality described by the interface. This is true of all subclasses of that class as well.

Interfaces are particularly useful for assigning common functionality to possibly unrelated classes. This allows objects of unrelated classes to be processed polymorphically—objects of classes that implement the same interface can respond to all of the interface method calls. To demonstrate creating and using interfaces, we modify our payroll application to create a general accounts payable application that can calculate payments due for company employees and invoice amounts to be billed for purchased goods. As you’ll see, interfaces enable polymorphic capabilities similar to those possible with inheritance.

G.7 Polymorphism: An Example

Space Objects in a Video Game

Suppose we design a video game that manipulates objects of classes Martian, Venusian, Plutonian, SpaceShip and LaserBeam. Imagine that each class inherits from the superclass SpaceObject, which contains method draw. Each subclass implements this method. A screen manager maintains a collection (e.g., a SpaceObject array) of references to objects of the various classes. To refresh the screen, the screen manager periodically sends each object the same message—namely, draw. However, each object responds its own way, based on its class. For example, a Martian object might draw itself in red with green eyes and the appropriate number of antennae. A SpaceShip object might draw itself as a bright silver flying saucer. A LaserBeam object might draw itself as a bright red beam across the screen. Again, the same message (in this case, draw) sent to a variety of objects has “many forms” of results.

A screen manager might use polymorphism to facilitate adding new classes to a system with minimal modifications to the system’s code. Suppose that we want to add Mercurian objects to our video game. To do so, we’d build a class Mercurian that extends SpaceObject and provides its own draw method implementation. When Mercurian objects appear in the SpaceObject collection, the screen manager code invokes method draw, exactly as it does for every other object in the collection, regardless of its type. So the new Mercurian objects simply “plug right in” without any modification of the screen manager code by the programmer. Thus, without modifying the system (other than to build new classes and modify the code that creates new objects), you can use polymorphism to conveniently include additional types that were not envisioned when the system was created.


Image Software Engineering Observation G.4

Polymorphism enables you to deal in generalities and let the execution-time environment handle the specifics. You can command objects to behave in manners appropriate to those objects, without knowing their types (as long as the objects belong to the same inheritance hierarchy).


 


Image Software Engineering Observation G.5

Polymorphism promotes extensibility: Software that invokes polymorphic behavior is independent of the object types to which messages are sent. New object types that can respond to existing method calls can be incorporated into a system without modifying the base system. Only client code that instantiates new objects must be modified to accommodate new types.


G.8 Demonstrating Polymorphic Behavior

Section G.4 created a class hierarchy, in which class BasePlusCommissionEmployee inherited from CommissionEmployee. The examples in that section manipulated CommissionEmployee and BasePlusCommissionEmployee objects by using references to them to invoke their methods—we aimed superclass variables at superclass objects and subclass variables at subclass objects. These assignments are natural and straightforward—superclass variables are intended to refer to superclass objects, and subclass variables are intended to refer to subclass objects. However, as you’ll soon see, other assignments are possible.

In the next example, we aim a superclass reference at a subclass object. We then show how invoking a method on a subclass object via a superclass reference invokes the subclass functionality—the type of the referenced object, not the type of the variable, determines which method is called. This example demonstrates that an object of a subclass can be treated as an object of its superclass, enabling various interesting manipulations. A program can create an array of superclass variables that refer to objects of many subclass types. This is allowed because each subclass object is an object of its superclass. For instance, we can assign the reference of a BasePlusCommissionEmployee object to a superclass CommissionEmployee variable, because a BasePlusCommissionEmployee is a CommissionEmployee—we can treat a BasePlusCommissionEmployee as a CommissionEmployee.

As you’ll learn later in this appendix, you cannot treat a superclass object as a subclass object, because a superclass object is not an object of any of its subclasses. For example, we cannot assign the reference of a CommissionEmployee object to a subclass BasePlusCommissionEmployee variable, because a CommissionEmployee is not a BasePlusCommissionEmployee—a CommissionEmployee does not have a baseSalary instance variable and does not have methods setBaseSalary and getBaseSalary. The is-a relationship applies only up the hierarchy from a subclass to its direct and indirect superclasses, and not vice versa (i.e., not down the hierarchy from a superclass to its subclasses).

The Java compiler does allow the assignment of a superclass reference to a subclass variable if we explicitly cast the superclass reference to the subclass type—a technique we discuss in Section G.10. Why would we ever want to perform such an assignment? A superclass reference can be used to invoke only the methods declared in the superclass—attempting to invoke subclass-only methods through a superclass reference results in compilation errors. If a program needs to perform a subclass-specific operation on a subclass object referenced by a superclass variable, the program must first cast the superclass reference to a subclass reference through a technique known as downcasting. This enables the program to invoke subclass methods that are not in the superclass. We show a downcasting example in Section G.10.

The example in Fig. G.13 demonstrates three ways to use superclass and subclass variables to store references to superclass and subclass objects. The first two are straightforward—as in Section G.4, we assign a superclass reference to a superclass variable, and a subclass reference to a subclass variable. Then we demonstrate the relationship between subclasses and superclasses (i.e., the is-a relationship) by assigning a subclass reference to a superclass variable. This program uses classes CommissionEmployee and BasePlusCommissionEmployee from Fig. G.10 and Fig. G.11, respectively.


 1   // Fig. G.13: PolymorphismTest.java
 2   // Assigning superclass and subclass references to superclass and
 3   // subclass variables.
 4
 5   public class PolymorphismTest
 6   {
 7      public static void main( String[] args )
 8      {
 9         // assign superclass reference to superclass variable          
10         CommissionEmployee commissionEmployee = new CommissionEmployee(
11            "Sue", "Jones", "222-22-2222", 10000, .06 );                
12
13         // assign subclass reference to subclass variable       
14         BasePlusCommissionEmployee basePlusCommissionEmployee = 
15            new BasePlusCommissionEmployee(                      
16            "Bob", "Lewis", "333-33-3333", 5000, .04, 300 );     
17
18         // invoke toString on superclass object using superclass variable
19         System.out.printf( "%s %s: %s ",
20            "Call CommissionEmployee's toString with superclass reference ",
21            "to superclass object", commissionEmployee.toString() );
22
23         // invoke toString on subclass object using subclass variable
24         System.out.printf( "%s %s: %s ",
25            "Call BasePlusCommissionEmployee's toString with subclass",
26            "reference to subclass object",
27            basePlusCommissionEmployee.toString() );
28
29         // invoke toString on subclass object using superclass variable
30         CommissionEmployee commissionEmployee2 = 
31            basePlusCommissionEmployee;           
32         System.out.printf( "%s %s: %s ",
33            "Call BasePlusCommissionEmployee's toString with superclass",
34            "reference to subclass object", commissionEmployee2.toString() );
35      } // end main
36   } // end class PolymorphismTest


Call CommissionEmployee's toString with superclass reference to superclass
object:

commission employee: Sue Jones
social security number: 222-22-2222
gross sales: 10000.00
commission rate: 0.06

Call BasePlusCommissionEmployee's toString with subclass reference to
subclass object:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00

Call BasePlusCommissionEmployee's toString with superclass reference to
subclass object:

base-salaried commission employee: Bob Lewis
social security number: 333-33-3333
gross sales: 5000.00
commission rate: 0.04
base salary: 300.00


Fig. G.13 | Assigning superclass and subclass references to superclass and subclass variables.

In Fig. G.13, lines 10–11 create a CommissionEmployee object and assign its reference to a CommissionEmployee variable. Lines 14–16 create a BasePlusCommissionEmployee object and assign its reference to a BasePlusCommissionEmployee variable. These assignments are natural—for example, a CommissionEmployee variable’s primary purpose is to hold a reference to a CommissionEmployee object. Lines 19–21 use commissionEmployee to invoke toString explicitly. Because commissionEmployee refers to a CommissionEmployee object, superclass CommissionEmployee’s version of toString is called. Similarly, lines 24–27 use basePlusCommissionEmployee to invoke toString explicitly on the BasePlusCommissionEmployee object. This invokes subclass BasePlusCommissionEmployee’s version of toString.

Lines 30–31 then assign the reference of subclass object basePlusCommissionEmployee to a superclass CommissionEmployee variable, which lines 32–34 use to invoke method toString. When a superclass variable contains a reference to a subclass object, and that reference is used to call a method, the subclass version of the method is called. Hence, commissionEmployee2.toString() in line 34 actually calls class BasePlusCommissionEmployee’s toString method. The Java compiler allows this “crossover” because an object of a subclass is an object of its superclass (but not vice versa). When the compiler encounters a method call made through a variable, the compiler determines if the method can be called by checking the variable’s class type. If that class contains the proper method declaration (or inherits one), the call is compiled. At execution time, the type of the object to which the variable refers determines the actual method to use. This process, called dynamic binding, is discussed in detail in Section G.10.

G.9 Abstract Classes and Methods

When we think of a class, we assume that programs will create objects of that type. Sometimes it’s useful to declare classes—called abstract classes—for which you never intend to create objects. Because they’re used only as superclasses in inheritance hierarchies, we refer to them as abstract superclasses. These classes cannot be used to instantiate objects, because, as we’ll soon see, abstract classes are incomplete. Subclasses must declare the “missing pieces” to become “concrete” classes, from which you can instantiate objects. Otherwise, these subclasses, too, will be abstract. We demonstrate abstract classes in Section G.10.

Purpose of Abstract Classes

An abstract class’s purpose is to provide an appropriate superclass from which other classes can inherit and thus share a common design. In the Shape hierarchy of Fig. G.3, for example, subclasses inherit the notion of what it means to be a Shape—perhaps common attributes such as location, color and borderThickness, and behaviors such as draw, move, resize and changeColor. Classes that can be used to instantiate objects are called concrete classes. Such classes provide implementations of every method they declare (some of the implementations can be inherited). For example, we could derive concrete classes Circle, Square and Triangle from abstract superclass TwoDimensionalShape. Similarly, we could derive concrete classes Sphere, Cube and Tetrahedron from abstract superclass ThreeDimensionalShape. Abstract superclasses are too general to create real objects—they specify only what is common among subclasses. We need to be more specific before we can create objects. For example, if you send the draw message to abstract class TwoDimensionalShape, the class knows that two-dimensional shapes should be drawable, but it does not know what specific shape to draw, so it cannot implement a real draw method. Concrete classes provide the specifics that make it reasonable to instantiate objects.

Not all hierarchies contain abstract classes. However, you’ll often write client code that uses only abstract superclass types to reduce the client code’s dependencies on a range of subclass types. For example, you can write a method with a parameter of an abstract superclass type. When called, such a method can receive an object of any concrete class that directly or indirectly extends the superclass specified as the parameter’s type.

Abstract classes sometimes constitute several levels of a hierarchy. For example, the Shape hierarchy of Fig. G.3 begins with abstract class Shape. On the next level of the hierarchy are abstract classes TwoDimensionalShape and ThreeDimensionalShape. The next level of the hierarchy declares concrete classes for TwoDimensionalShapes (Circle, Square and Triangle) and for ThreeDimensionalShapes (Sphere, Cube and Tetrahedron).

Declaring an Abstract Class and Abstract Methods

You make a class abstract by declaring it with keyword abstract. An abstract class normally contains one or more abstract methods. An abstract method is one with keyword abstract in its declaration, as in

public abstract void draw(); // abstract method

Abstract methods do not provide implementations. A class that contains any abstract methods must be explicitly declared abstract even if that class contains some concrete (nonabstract) methods. Each concrete subclass of an abstract superclass also must provide concrete implementations of each of the superclass’s abstract methods. Constructors and static methods cannot be declared abstract. Constructors are not inherited, so an abstract constructor could never be implemented. Though non-private static methods are inherited, they cannot be overridden. Since abstract methods are meant to be overridden so that they can process objects based on their types, it would not make sense to declare a static method as abstract.


Image Software Engineering Observation G.6

An abstract class declares common attributes and behaviors (both abstract and concrete) of the various classes in a class hierarchy. An abstract class typically contains one or more abstract methods that subclasses must override if they are to be concrete. The instance variables and concrete methods of an abstract class are subject to the normal rules of inheritance.


Using Abstract Classes to Declare Variables

Although we cannot instantiate objects of abstract superclasses, you’ll soon see that we can use abstract superclasses to declare variables that can hold references to objects of any concrete class derived from those abstract superclasses. Programs typically use such variables to manipulate subclass objects polymorphically. You also can use abstract superclass names to invoke static methods declared in those abstract superclasses.

Consider another application of polymorphism. A drawing program needs to display many shapes, including types of new shapes that you’ll add to the system after writing the drawing program. The drawing program might need to display shapes, such as Circles, Triangles, Rectangles or others, that derive from abstract class Shape. The drawing program uses Shape variables to manage the objects that are displayed. To draw any object in this inheritance hierarchy, the drawing program uses a superclass Shape variable containing a reference to the subclass object to invoke the object’s draw method. This method is declared abstract in superclass Shape, so each concrete subclass must implement method draw in a manner specific to that shape—each object in the Shape inheritance hierarchy knows how to draw itself. The drawing program does not have to worry about the type of each object or whether the program has ever encountered objects of that type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.176.88