In this section, we 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 divide our discussion of the relationship between commission employees and base-salaried commission employees into five examples. The first example declares class CommissionEmployee, which directly inherits from class Object and declares as private instance variables a first name, last name, social security number, commission rate and gross (i.e., total) sales amount.
The second example declares class BasePlusCommissionEmployee, which also directly inherits from class Object and declares as private instance variables a first name, last name, social security number, commission rate, gross sales amount and base salary. We create the latter class by writing every line of code the class requireswe will soon see that it is much more efficient to create this class by inheriting from class CommissionEmployee.
The third example declares a separate BasePlusCommissionEmployee2 class that extends class CommissionEmployee (i.e., a BasePlusCommissionEmployee2 is a CommissionEmployee who also has a base salary) and attempts to access class CommissionEmployee's private membersthis results in compilation errors, because the subclass cannot access the superclass's private instance variables.
The fourth example shows that if CommissionEmployee's instance variables are declared as protected, a BasePlusCommissionEmployee3 class that extends class CommissionEmployee2 can access that data directly. For this purpose, we declare class CommissionEmployee2 with protected instance variables. Both of the BasePlusCommissionEmployee classes contain identical functionality, but we show how the class BasePlusCommissionEmployee3 is easier to create and manage.
After we discuss the convenience of using protected instance variables, we create the fifth example, which sets the CommissionEmployee instance variables back to private in class CommissionEmployee3 to enforce good software engineering. Then we show how a separate BasePlusCommissionEmployee4 class, which extends class CommissionEmployee3, can use CommissionEmployee3's public methods to manipulate CommissionEmployee3's private instance variables.
9.4.1. Creating and Using a CommissionEmployee Class
We begin by declaring class CommissionEmployee (Fig. 9.4). Line 4 begins the class declaration and indicates that class CommissionEmployee extends (i.e., inherits from) class Object (from package java.lang). Java programmers use inheritance to create classes from existing classes. In fact, every class in Java (except Object) extends an existing class. Because class CommissionEmployee extends class Object, class CommissionEmployee inherits the methods of class Objectclass Object does not have any fields. In fact, every Java class directly or indirectly inherits Object's methods. If a class does not specify that it extends another class, the new class implicitly extends Object. For this reason, programmers typically do not include "extends Object" in their codewe do so in this example for demonstration purposes.
Figure 9.4. CommissionEmployee class represents an employee paid a percentage of gross sales.
(This item is displayed on pages 421 - 423 in the print version)
1 // Fig. 9.4: CommissionEmployee.java 2 // CommissionEmployee class represents a commission employee. 3 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; 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; 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 grossSales = ( sales < 0.0 ) ? 0.0 : sales; 64 } // end method setGrossSales 65 66 // return gross sales amount 67 public double getGrossSales() 68 { 69 return grossSales; 70 } // end method getGrossSales 71 72 // set commission rate 73 public void setCommissionRate( double rate ) 74 { 75 commissionRate = ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0; 76 } // end method setCommissionRate 77 78 // return commission rate 79 public double getCommissionRate() 80 { 81 return commissionRate; 82 } // end method getCommissionRate 83 84 // calculate earnings 85 public double earnings() 86 { 87 return commissionRate * grossSales; 88 } // end method earnings 89 90 // return String representation of CommissionEmployee object 91 public String toString() 92 { 93 return String.format( "%s: %s %s %s: %s %s: %.2f %s: %.2f", 94 "commission employee", firstName, lastName, 95 "social security number", socialSecurityNumber, 96 "gross sales", grossSales, 97 "commission rate", commissionRate ); 98 } // end method toString 99 } // end class CommissionEmployee |
Software Engineering Observation 9.3
The Java compiler sets the superclass of a class to Object when the class declaration does not explicitly extend a superclass. |
The public services of class CommissionEmployee include a constructor (lines 1322) and methods earnings (lines 8588) and toString (lines 9198). Lines 2582 declare public get and set methods for manipulating the class's instance variables (declared in lines 610) firstName, lastName, socialSecurityNumber, grossSales and commissionRate. Class CommissionEmployee declares each of 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 the instance variables 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.
Constructors are not inherited, so class CommissionEmployee does not inherit class Object's constructor. However, class CommissionEmployee's constructor calls class Object's constructor implicitly. 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. The syntax for calling a superclass constructor explicitly is discussed in Section 9.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. 9.4 indicates where the implicit call to the superclass Object's default constructor is made (the programmer does not write the code for this call). Object's default (empty) constructor does nothing. Note that 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 occurs, lines 1721 of CommissionEmployee's constructor assign values to the class's instance variables. Note that we do not validate the values of arguments first, last and ssn before assigning them to the corresponding instance variables. While validating data is good software engineering, including extensive validation in this class could add a potentially large amount of code that would obscure the focus of this example. We certainly could validate the first and last namesperhaps by ensuring that they are of a reasonable length. Similarly, a social security number could be validated to ensure that it contains nine digits, with or without dashes (e.g., 123-45-6789 or 123456789).
Method earnings (lines 8588) calculates a CommissionEmployee's earnings. Line 87 multiplies the commissionRate by the grossSales and returns the result.
Method toString (lines 9198) is specialit is one of the methods that every class inherits directly or indirectly from class Object, which is the root of the Java class hierarchy. Section 9.7 summarizes class Object's methods. Method toString returns a String representing an object. This method is called implicitly by a program whenever an object must be converted to a string representation, such as when an object is output by printf or String method format using the %s format specifier. Class Object's toString method returns a String that includes the name of the object's class. It is 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. We use format specifier %.2f to format both the grossSales and the commissionRate with two digits of precision to the right of the decimal point. To override a superclass method, a subclass must declare a method with the same signature (method name, number of parameters and parameter types) as the superclass methodObject's toString method takes no parameters, so CommissionEmployee declares toString with no parameters.
Common Programming Error 9.1
It is a syntax error to override a method with a more restricted access modifiera 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 is 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 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. |
Figure 9.5 tests class CommissionEmployee. Lines 910 instantiate a CommissionEmployee object and invoke CommissionEmployee's constructor (lines 1322 of Fig. 9.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 1524 use CommissionEmployee's get methods to retrieve the object's instance variable values for output. Lines 2627 invoke the object's methods setGrossSales and setCommissionRate to change the values of instance variables grossSales and commissionRate. Lines 2930 output the string representation of the updated CommissionEmployee. Note that 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.
Figure 9.5. CommissionEmployee class test program.
(This item is displayed on pages 424 - 425 in the print version)
1 // Fig. 9.5: CommissionEmployeeTest.java 2 // Testing class CommissionEmployee. 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
|
9.4.2. Creating a BasePlusCommissionEmployee Class without Using Inheritance
We now discuss the second part of our introduction to inheritance by declaring and testing (a completely new and independent) class BasePlusCommissionEmployee (Fig. 9.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 1525) and methods earnings (lines 100103) and toString (lines 106114). Lines 2897 declare public get and set methods for the class's private instance variables (declared in lines 712) 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. 9.4)in this example, we will not yet exploit that similarity.
Figure 9.6. BasePlusCommissionEmployee class represents an employee who receives a base salary in addition to a commission.
(This item is displayed on pages 426 - 428 in the print version)
1 // Fig. 9.6: BasePlusCommissionEmployee.java 2 // BasePlusCommissionEmployee class represents an employee that receives 3 // a base salary in addition to 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; 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; 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 grossSales = ( sales < 0.0 ) ? 0.0 : sales; 67 } // end method setGrossSales 68 69 // return gross sales amount 70 public double getGrossSales() 71 { 72 return grossSales; 73 } // end method getGrossSales 74 75 // set commission rate 76 public void setCommissionRate( double rate ) 77 { 78 commissionRate = ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0; 79 } // end method setCommissionRate 80 81 // return commission rate 82 public double getCommissionRate() 83 { 84 return commissionRate; 85 } // end method getCommissionRate 86 87 // set base salary 88 public void setBaseSalary( double salary ) 89 { 90 baseSalary = ( salary < 0.0 ) ? 0.0 : salary; 91 } // end method setBaseSalary 92 93 // return base salary 94 public double getBaseSalary() 95 { 96 return baseSalary; 97 } // end method getBaseSalary 98 99 // calculate earnings 100 public double earnings() 101 { 102 return baseSalary + ( commissionRate * grossSales ); 103 } // end method earnings 104 105 // return String representation of BasePlusCommissionEmployee 106 public String toString() 107 { 108 return String.format( 109 "%s: %s %s %s: %s %s: %.2f %s: %.2f", 110 "base-salaried commission employee", firstName, lastName, 111 "social security number", socialSecurityNumber, 112 "gross sales", grossSales, "commission rate", commissionRate, 113 "base salary", baseSalary ); 114 } // end method toString 115 } // end class BasePlusCommissionEmployee |
Note that class BasePlusCommissionEmployee does not specify "extends Object" on line 5, so the class implicitly extends Object. Also note that, like class CommissionEmployee's constructor (lines 1322 of Fig. 9.4), class BasePlusCommissionEmployee's constructor invokes class Object's default constructor implicitly, as noted in the comment on line 18.
Class BasePlusCommissionEmployee's earnings method (lines 100103) computes the earnings of a base-salaried commission employee. Line 102 returns the result of adding the employee'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 109).
Figure 9.7 tests class BasePlusCommissionEmployee. Lines 911 instantiate 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 1627 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. 9.6, lines 8891) ensures that instance variable baseSalary is not assigned a negative value, because an employee's base salary cannot be negative. Lines 3133 of Fig. 9.7 invoke the object's toString method explicitly to get the object's string representation.
Figure 9.7. BasePlusCommissionEmployee test program.
(This item is displayed on page 429 in the print version)
1 // Fig. 9.7: BasePlusCommissionEmployeeTest.java 2 // Testing class BasePlusCommissionEmployee. 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
|
Note that much of the code for class BasePlusCommissionEmployee (Fig. 9.6) is similar, if not identical, to the code for class CommissionEmployee (Fig. 9.4). For example, in class BasePlusCommissionEmployee, private instance variables firstName and lastName and methods setFirstName, getFirstName, setLastName and getLastName are identical to those of class CommissionEmployee. Classes CommissionEmployee and BasePlusCommissionEmployee also both contain private instance variables socialSecurityNumber, commissionRate and grossSales, as well as get and set methods to manipulate these variables. 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 BasePlusCommissionEmployee's toString also outputs the value of instance variable baseSalary with two digits of precision to the right of the decimal point.
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 can spread many physical 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? In the next several examples, we answer this question, using a more elegant approach to building classes that emphasizes the benefits of inheritance.
Software Engineering Observation 9.4
Copying and pasting code from one class to another can spread errors across multiple source code files. To avoid duplicating code (and possibly errors), use inheritance, rather than the "copy-andpaste" approach, in situations where you want one class to "absorb" the instance variables and methods of another class. |
Software Engineering Observation 9.5
With inheritance, the common instance variables and methods of all the classes in the hierarchy are declared in a superclass. When changes are required for these common features, software developers need only to make the changes in the superclasssubclasses 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. |
9.4.3. Creating a CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy
Now we declare class BasePlusCommissionEmployee2 (Fig. 9.8), which extends class CommissionEmployee (Fig. 9.4). A BasePlusCommissionEmployee2 object is a CommissionEmployee (because inheritance passes on the capabilities of class CommissionEmployee), but class BasePlusCommissionEmployee2 also has instance variable baseSalary (Fig. 9.8, line 6). Keyword extends in line 4 of the class declaration indicates inheritance. As a subclass, BasePlusCommissionEmployee2 inherits the public and protected instance variables and methods of class CommissionEmployee. The constructor of class CommissionEmployee is not inherited. Thus, the public services of BasePlusCommissionEmployee2 include its constructor (lines 916), public methods inherited from class CommissionEmployee, method setBaseSalary (lines 1922), method getBaseSalary (lines 2528), method earnings (lines 3135) and method toString (lines 3847).
Figure 9.8. private superclass members cannot be accessed in a subclass.
(This item is displayed on pages 431 - 432 in the print version)
1 // Fig. 9.8: BasePlusCommissionEmployee2.java 2 // BasePlusCommissionEmployee2 inherits from class CommissionEmployee. 3 4 public class BasePlusCommissionEmployee2 extends CommissionEmployee 5 { 6 private double baseSalary; // base salary per week 7 8 // six-argument constructor 9 public BasePlusCommissionEmployee2( 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( amount ); // validate and store base salary 16 } // end six-argument BasePlusCommissionEmployee2 constructor 17 18 // set base salary 19 public void setBaseSalary( double salary ) 20 { 21 baseSalary = ( salary < 0.0 ) ? 0.0 : salary; 22 } // end method setBaseSalary 23 24 // return base salary 25 public double getBaseSalary() 26 { 27 return baseSalary; 28 } // end method getBaseSalary 29 30 // calculate earnings 31 public double earnings() 32 { 33 // not allowed: commissionRate and grossSales private in superclass 34 return baseSalary + ( commissionRate * grossSales ); 35 } // end method earnings 36 37 // return String representation of BasePlusCommissionEmployee2 38 public String toString() 39 { 40 // not allowed: attempts to access private superclass members 41 return String.format( 42 "%s: %s %s %s: %s %s: %.2f %s: %.2f %s: %.2f", 43 "base-salaried commission employee", firstName, lastName, 44 "social security number", socialSecurityNumber, 45 "gross sales", grossSales, "commission rate", commissionRate, 46 "base salary", baseSalary ); 47 } // end method toString 48 } // end class BasePlusCommissionEmployee2
|
Each subclass constructor must implicitly or explicitly call its superclass constructor to ensure that the instance variables inherited from the superclass are initialized properly. BasePlusCommissionEmployee2's six-argument constructor (lines 916) explicitly calls class CommissionEmployee's five-argument constructor to initialize the superclass portion of a BasePlusCommissionEmployee2 object (i.e., variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate). Line 13 in BasePlusCommissionEmployee2's six-argument constructor invokes the CommissionEmployee's five-argument constructor (declared at lines 1322 of Fig. 9.4) by using the superclass constructor call syntaxkeyword 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 BasePlusCommissionEmployee2's constructor did not invoke CommissionEmployee's constructor explicitly, Java would attempt to invoke class CommissionEmployee's no-argument or default constructorbut the class does not have such a constructor, so the compiler would issue an error. The explicit superclass constructor call on line 13 must be the first statement in the subclass constructor's body. Also, when a superclass contains a no-argument constructor, you can use super() to call that constructor explicitly, but this is rarely done.
Common Programming Error 9.2
A compilation error occurs if a subclass constructor calls one of its superclass constructors with arguments that do not match exactly the number and types of parameters specified in one of the superclass constructor declarations. |
The compiler generates errors for line 34 of Fig. 9.8 because superclass CommissionEmployee's instance variables commissionRate and grossSales are privatesubclass BasePlusCommissionEmployee2's methods are not allowed to access superclass CommissionEmployee's private instance variables. Note that we used red text in Fig. 9.8 to indicate erroneous code. The compiler issues additional errors at lines 4345 of BasePlusCommissionEmployee2's toString method for the same reason. The errors in BasePlusCommissionEmployee2 could have been prevented by using the get methods inherited from class CommissionEmployee. For example, line 34 could have used getCommissionRate and getGrossSales to access CommissionEmployee's private instance variables commissionRate and grossSales, respectively. Lines 4345 also could have used appropriate get methods to retrieve the values of the superclass's instance variables.
9.4.4. CommissionEmployeeBasePlusCommissionEmployee 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 9.3, a superclass's protected members are inherited by all subclasses of that superclass. Class CommissionEmployee2 (Fig. 9.9) is a modification of class CommissionEmployee (Fig. 9.4) that declares instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate as protected (Fig. 9.9, lines 610) rather than private. Other than the change in the class name (and thus the change in the constructor name) to CommissionEmployee2, the rest of the class declaration in Fig. 9.9 is identical to that of Fig. 9.4.
Figure 9.9. CommissionEmployee2 with protected instance variables.
(This item is displayed on pages 433 - 435 in the print version)
1 // Fig. 9.9: CommissionEmployee2.java 2 // CommissionEmployee2 class represents a commission employee. 3 4 public class CommissionEmployee2 5 { 6 protected String firstName; 7 protected String lastName; 8 protected String socialSecurityNumber; 9 protected double grossSales; // gross weekly sales 10 protected double commissionRate; // commission percentage 11 12 // five-argument constructor 13 public CommissionEmployee2 ( 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 CommissionEmployee2 constructor 23 24 // set first name 25 public void setFirstName( String first ) 26 { 27 firstName = first; 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; 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 grossSales = ( sales < 0.0 ) ? 0.0 : sales; 64 } // end method setGrossSales 65 66 // return gross sales amount 67 public double getGrossSales() 68 { 69 return grossSales; 70 } // end method getGrossSales 71 72 // set commission rate 73 public void setCommissionRate( double rate ) 74 { 75 commissionRate = ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0; 76 } // end method setCommissionRate 77 78 // return commission rate 79 public double getCommissionRate() 80 { 81 return commissionRate; 82 } // end method getCommissionRate 83 84 // calculate earnings 85 public double earnings() 86 { 87 return commissionRate * grossSales; 88 } // end method earnings 89 90 // return String representation of CommissionEmployee2 object 91 public String toString() 92 { 93 return String.format( "%s: %s %s %s: %s %s: %.2f %s: %.2f", 94 "commission employee", firstName, lastName, 95 "social security number", socialSecurityNumber, 96 "gross sales", grossSales, 97 "commission rate", commissionRate ); 98 } // end method toString 99 } // end class CommissionEmployee2 |
We could have declared the superclass CommissionEmployee2's instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate as public to enable subclass BasePlusCommissionEmployee2 to access the superclass instance variables. However, declaring public instance variables is poor software engineering because it allows unrestricted access to the instance 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 BasePlusCommissionEmployee3 (Fig. 9.10) is a modification of class BasePlusCommissionEmployee2 (Fig. 9.8) that extends CommissionEmployee2 (line 5) rather than class CommissionEmployee. Objects of class BasePlusCommissionEmployee3 inherit CommissionEmployee2's protected instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRateall these variables are now protected members of BasePlusCommissionEmployee3. As a result, the compiler does not generate errors when compiling line 32 of method earnings and lines 4042 of method toString. If another class extends BasePlusCommissionEmployee3, the new subclass also inherits the protected members.
Figure 9.10. BasePlusCommissionEmployee3 inherits protected instance variables from CommissionEmployee2.
1 // Fig. 9.10: BasePlusCommissionEmployee3.java 2 // BasePlusCommissionEmployee3 inherits from CommissionEmployee2 and has 3 // access to CommissionEmployee2's protected members. 4 5 public class BasePlusCommissionEmployee3 extends CommissionEmployee2 6 { 7 private double baseSalary; // base salary per week 8 9 // six-argument constructor 10 public BasePlusCommissionEmployee3( 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 BasePlusCommissionEmployee3 constructor 16 17 // set base salary 18 public void setBaseSalary( double salary ) 19 { 20 baseSalary = ( salary < 0.0 ) ? 0.0 : salary; 21 } // end method setBaseSalary 22 23 // return base salary 24 public double getBaseSalary() 25 { 26 return baseSalary; 27 } // end method getBaseSalary 28 29 // calculate earnings 30 public double earnings() 31 { 32 return baseSalary + ( commissionRate * grossSales ); 33 } // end method earnings 34 35 // return String representation of BasePlusCommissionEmployee3 36 public String toString() 37 { 38 return String.format( 39 "%s: %s %s %s: %s %s: %.2f %s: %.2f %s: %.2f", 40 "base-salaried commission employee", firstName, lastName, 41 "social security number", socialSecurityNumber, 42 "gross sales", grossSales, "commission rate", commissionRate, 43 "base salary", baseSalary ); 44 } // end method toString 45 } // end class BasePlusCommissionEmployee3 |
Class BasePlusCommissionEmployee3 does not inherit class CommissionEmployee2's constructor. However, class BasePlusCommissionEmployee3's six-argument constructor (lines 1015) calls class CommissionEmployee2's five-argument constructor explicitly. BasePlusCommissionEmployee3's six-argument constructor must explicitly call the five-argument constructor of class CommissionEmployee2, because CommissionEmployee2 does not provide a no-argument constructor that could be invoked implicitly.
Figure 9.11 uses a BasePlusCommissionEmployee3 object to perform the same tasks that Fig. 9.7 performed on a BasePlusCommissionEmployee object (Fig. 9.6). Note that the outputs of the two programs are identical. Although we declared class BasePlusCommissionEmployee without using inheritance and declared class BasePlusCommissionEmployee3 using inheritance, both classes provide the same functionality. The source code for class BasePlusCommissionEmployee3, which is 45 lines, is considerably shorter than that for class BasePlusCommissionEmployee, which is 115 lines, because class BasePlusCommissionEmployee3 inherits most of its functionality from CommissionEmployee2, whereas class BasePlusCommissionEmployee inherits only class Object's functionality. Also, there is now only one copy of the commission employee functionality declared in class CommissionEmployee2. This makes the code easier to maintain, modify and debug, because the code related to a commission employee exists only in class CommissionEmployee2.
Figure 9.11. protected superclass members inherited into subclass BasePlusCommissionEmployee3.
(This item is displayed on pages 437 - 438 in the print version)
1 // Fig. 9.11: BasePlusCommissionEmployeeTest3.java 2 // Testing class BasePlusCommissionEmployee3. 3 4 public class BasePlusCommissionEmployeeTest3 5 { 6 public static void main( String args[] ) 7 { 8 // instantiate BasePlusCommissionEmployee3 object 9 BasePlusCommissionEmployee3 employee = 10 new BasePlusCommissionEmployee3( 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 BasePlusCommissionEmployeeTest3
|
In this example, we declared superclass instance variables as protected so that subclasses could inherit 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 is 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, thus leaving the object in an inconsistent state. For example, if we were to declare CommissionEmployee3's instance variable grossSales as protected, a subclass object (e.g., BasePlusCommissionEmployee) could then assign a negative value to grossSales. The second 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. The programmer 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 membersthis is not always desirable.
Software Engineering Observation 9.6
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. |
Software Engineering Observation 9.7
Declaring superclass instance variables private (as opposed to protected) enables the superclass implementation of these instance variables to change without affecting subclass implementations. |
Error-Prevention Tip 9.1
When possible, do not include protected instance variables in a superclass. Instead, include non-private methods that access private instance variables. This will ensure that objects of the class maintain consistent states. |
9.4.5. CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy Using private Instance Variables
We now reexamine our hierarchy once more, this time using the best software engineering practices. Class CommissionEmployee3 (Fig. 9.12) declares instance variables firstName, lastName, socialSecurityNumber, grossSales and commissionRate as private (lines 610) and provides public methods setFirstName, getFirstName, setLastName, getLastName, setSocialSecurityNumber, getSocialSecurityNumber, setGrossSales, getGrossSales, setCommissionRate, getCommissionRate, earnings and toString for manipulating these values. Note that methods earnings (lines 8588) and toString (lines 9198) 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 modificationonly the bodies of the get and set methods that directly manipulate the instance variables will need to change. Note that these changes occur solely within the superclassno changes to the subclass are needed. Localizing the effects of changes like this is a good software engineering practice. Subclass BasePlusCommissionEmployee4 (Fig. 9.13) inherits CommissionEmployee3's non-private methods and can access the private superclass members via those methods.
Figure 9.12. CommissionEmployee3 class uses methods to manipulate its private instance variables.
(This item is displayed on pages 439 - 441 in the print version)
1 // Fig. 9.12: CommissionEmployee3.java 2 // CommissionEmployee3 class represents a commission employee. 3 4 public class CommissionEmployee3 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 CommissionEmployee3( 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 CommissionEmployee3 constructor 23 24 // set first name 25 public void setFirstName( String first ) 26 { 27 firstName = first; 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; 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 grossSales = ( sales < 0.0 ) ? 0.0 : sales; 64 } // end method setGrossSales 65 66 // return gross sales amount 67 public double getGrossSales() 68 { 69 return grossSales; 70 } // end method getGrossSales 71 72 // set commission rate 73 public void setCommissionRate( double rate ) 74 { 75 commissionRate = ( rate > 0.0 && rate < 1.0 ) ? rate : 0.0; 76 } // end method setCommissionRate 77 78 // return commission rate 79 public double getCommissionRate() 80 { 81 return commissionRate; 82 } // end method getCommissionRate 83 84 // calculate earnings 85 public double earnings() 86 { 87 return getCommissionRate() * getGrossSales(); 88 } // end method earnings 89 90 // return String representation of CommissionEmployee3 object 91 public String toString() 92 { 93 return String.format( "%s: %s %s %s: %s %s: %.2f %s: %.2f", 94 "commission employee", getFirstName(), getLastName(), 95 "social security number", getSocialSecurityNumber(), 96 "gross sales", getGrossSales(), 97 "commission rate", getCommissionRate() ); 98 } // end method toString 99 } // end class CommissionEmployee3 |
Figure 9.13. BasePlusCommissionEmployee4 class extends CommissionEmployee3, which provides only private instance variables.
(This item is displayed on page 442 in the print version)
1 // Fig. 9.13: BasePlusCommissionEmployee4.java 2 // BasePlusCommissionEmployee4 class inherits from CommissionEmployee3 and 3 // accesses CommissionEmployee3's private data via CommissionEmployee3's 4 // public methods. 5 6 public class BasePlusCommissionEmployee4 extends CommissionEmployee3 7 { 8 private double baseSalary; // base salary per week 9 10 // six-argument constructor 11 public BasePlusCommissionEmployee4( 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 BasePlusCommissionEmployee4 constructor 17 18 // set base salary 19 public void setBaseSalary( double salary ) 20 { 21 baseSalary = ( salary < 0.0 ) ? 0.0 : salary; 22 } // end method setBaseSalary 23 24 // return base salary 25 public double getBaseSalary() 26 { 27 return baseSalary; 28 } // end method getBaseSalary 29 30 // calculate earnings 31 public double earnings() 32 { 33 return getBaseSalary() + super.earnings(); 34 } // end method earnings 35 36 // return String representation of BasePlusCommissionEmployee4 37 public String toString() 38 { 39 return String.format( "%s %s %s: %.2f", "base-salaried", 40 super.toString(), "base salary", getBaseSalary() ); 41 } // end method toString 42 } // end class BasePlusCommissionEmployee4 |
Class BasePlusCommissionEmployee4 (Fig. 9.13) has several changes to its method implementations that distinguish it from class BasePlusCommissionEmployee3 (Fig. 9.10). Methods earnings (Fig. 9.13, lines 3134) and toString (lines 3741) 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.
Class BasePlusCommissionEmployee4's earnings method (Fig. 9.13, lines 3134) overrides class CommissionEmployee3's earnings method (Fig. 9.12, lines 8588) to calculate the earnings of a base-salaried commission employee. The new version obtains the portion of the employee's earnings based on commission alone by calling CommissionEmployee3's earnings method with the expression super.earnings() (Fig. 9.13, line 33). BasePlusCommissionEmployee4's earnings method then adds the base salary to this value to calculate the total earnings of the employee. Note the syntax used to invoke an overridden superclass method from a subclassplace the keyword super and a dot (.) separator before the superclass method name. This method invocation is a good software-engineering practice: Recall from Software Engineering Observation 8.5 that if a method performs all or some of the actions needed by another method, call that method rather than duplicate its code. By having BasePlusCommissionEmployee4's earnings method invoke CommissionEmployee3's earnings method to calculate part of a BasePlusCommissionEmployee4 object's earnings, we avoid duplicating the code and reduce code-maintenance problems.
Common Programming Error 9.3
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 referencing the superclass's method causes the subclass method to call itself, creating an error called infinite recursion. Recursion, used correctly, is a powerful capability discussed in Chapter 15, Recursion. |
Similarly, BasePlusCommissionEmployee4's toString method (Fig. 9.13, lines 3741) overrides class CommissionEmployee3's toString method (Fig. 9.12, lines 9198) to return a string representation that is appropriate for a base-salaried commission employee. The new version creates part of a BasePlusCommissionEmployee4 object's string representation (i.e., the string "commission employee" and the values of class CommissionEmployee3's private instance variables) by calling CommissionEmployee3's toString method with the expression super.toString() (Fig. 9.13, line 40). BasePlusCommissionEmployee4's toString method then outputs the remainder of a BasePlusCommissionEmployee4 object's string representation (i.e., the value of class BasePlusCommissionEmployee4's base salary).
Figure 9.14 performs the same manipulations on a BasePlusCommissionEmployee4 object as did Fig. 9.7 and Fig. 9.11 on objects of classes BasePlusCommissionEmployee and BasePlusCommissionEmployee3, respectively. Although each "base-salaried commission employee" class behaves identically, class BasePlusCommissionEmployee4 is the best engineered. By using inheritance and by calling methods that hide the data and ensure consistency, we have efficiently and effectively constructed a well-engineered class.
Figure 9.14. Superclass private instance variables are accessible to a subclass via public or protected methods inherited by the subclass.
(This item is displayed on pages 443 - 444 in the print version)
1 // Fig. 9.14: BasePlusCommissionEmployeeTest4.java 2 // Testing class BasePlusCommissionEmployee4. 3 4 public class BasePlusCommissionEmployeeTest4 5 { 6 public static void main( String args[] ) 7 { 8 // instantiate BasePlusCommissionEmployee4 object 9 BasePlusCommissionEmployee4 employee = 10 new BasePlusCommissionEmployee4( 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 BasePlusCommissionEmployeeTest4
|
In this section, you saw an evolutionary set of examples that was carefully designed to teach key capabilities for good software engineering with inheritance. You learned how to use the keyword extends to create a subclass using inheritance, how to use protected superclass members to enable a subclass to access inherited superclass instance variables and how to override superclass methods to provide versions that are more appropriate for subclass objects. In addition, you learned how to apply software-engineering techniques from Chapter 8 and this chapter to create classes that are easy to maintain, modify and debug.
Introduction to Computers, the Internet and the World Wide Web
Introduction to Java Applications
Introduction to Classes and Objects
Control Statements: Part I
Control Statements: Part 2
Methods: A Deeper Look
Arrays
Classes and Objects: A Deeper Look
Object-Oriented Programming: Inheritance
Object-Oriented Programming: Polymorphism
GUI Components: Part 1
Graphics and Java 2D™
Exception Handling
Files and Streams
Recursion
Searching and Sorting
Data Structures
Generics
Collections
Introduction to Java Applets
Multimedia: Applets and Applications
GUI Components: Part 2
Multithreading
Networking
Accessing Databases with JDBC
Servlets
JavaServer Pages (JSP)
Formatted Output
Strings, Characters and Regular Expressions
Appendix A. Operator Precedence Chart
Appendix B. ASCII Character Set
Appendix C. Keywords and Reserved Words
Appendix D. Primitive Types
Appendix E. (On CD) Number Systems
Appendix F. (On CD) Unicode®
Appendix G. Using the Java API Documentation
Appendix H. (On CD) Creating Documentation with javadoc
Appendix I. (On CD) Bit Manipulation
Appendix J. (On CD) ATM Case Study Code
Appendix K. (On CD) Labeled break and continue Statements
Appendix L. (On CD) UML 2: Additional Diagram Types
Appendix M. (On CD) Design Patterns
Appendix N. Using the Debugger
Inside Back Cover