AhmadLang / Java, How To Program, 2004
.pdf
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).
[Page 431]
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.
4public class BasePlusCommissionEmployee2 extends CommissionEmployee
5 |
{ |
6 |
private double baseSalary; // base salary per week |
7 |
|
8// six-argument constructor
9public BasePlusCommissionEmployee2( String first, String last,
10String ssn, double sales, double rate, double salary )
11{
12// explicit call to superclass CommissionEmployee constructor
13super( first, last, ssn, sales, rate );
14
15setBaseSalary( amount ); // validate and store base salary
16} // end six-argument BasePlusCommissionEmployee2 constructor
18// set base salary
19public void setBaseSalary( double salary )
20{
21baseSalary = ( salary < 0.0 ) ? 0.0 : salary;
22} // end method setBaseSalary
23
24// return base salary
25public double getBaseSalary()
26{
27return baseSalary;
28 } // end method getBaseSalary
29
30// calculate earnings
31public double earnings()
32{
33// not allowed: commissionRate and grossSales private in superclass
34return baseSalary + ( commissionRate * grossSales );
35} // end method earnings
36
37// return String representation of BasePlusCommissionEmployee2
38public String toString()
39{
40// not allowed: attempts to access private superclass members
41return String.format(
42"%s: %s %s\n%s: %s\n%s: %.2f\n%s: %.2f\n%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
BasePlusCommissionEmployee2.java:34: commissionRate has private access in CommissionEmployee
return baseSalary + ( commissionRate * grossSales );
^
BasePlusCommissionEmployee2.java:34: grossSales has private access in CommissionEmployee
return baseSalary + ( commissionRate * grossSales );
^
BasePlusCommissionEmployee2.java:43: firstName has private access in CommissionEmployee
"base-salaried commission employee", firstName, lastName,
^
BasePlusCommissionEmployee2.java:43: lastName has private access in CommissionEmployee
"base-salaried commission employee", firstName, lastName,
^
BasePlusCommissionEmployee2.java:44: socialSecurityNumber has private access in CommissionEmployee
"social security number", socialSecurityNumber,
^
BasePlusCommissionEmployee2.java:45: grossSales has private access in CommissionEmployee
"gross sales", grossSales, "commission rate", commissionRate,
^
BasePlusCommissionEmployee2.java:45: commissionRate has private access in CommissionEmployee
"gross sales", grossSales, "commission rate", commissionRate,
^
7 errors
[Page 432]
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 sixargument 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.
[Page 433]
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.
[Page 435]
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 |
|
|
4public class CommissionEmployee2
5{
6protected String firstName;
7protected String lastName;
8protected String socialSecurityNumber;
9protected double grossSales; // gross weekly sales
10protected double commissionRate; // commission percentage
12// five-argument constructor
13public CommissionEmployee2 ( String first, String last, String ssn,
14double sales, double rate )
15{
16// implicit call to Object constructor occurs here
17firstName = first;
18lastName = last;
19socialSecurityNumber = ssn;
20setGrossSales( sales ); // validate and store gross sales
21setCommissionRate( rate ); // validate and store commission rate
22} // end five-argument CommissionEmployee2 constructor
23
24// set first name
25public void setFirstName( String first )
26{
27firstName = first;
28} // end method setFirstName
29
30// return first name
31public String getFirstName()
32{
33return firstName;
34} // end method getFirstName
36// set last name
37public void setLastName( String last )
38{
39lastName = last;
40} // end method setLastName
42// return last name
43public String getLastName()
44{
45return lastName;
46} // end method getLastName
48// set social security number
49public void setSocialSecurityNumber( String ssn )
50{
51socialSecurityNumber = ssn; // should validate
52} // end method setSocialSecurityNumber
53
54// return social security number
55public String getSocialSecurityNumber()
56{
57return socialSecurityNumber;
58} // end method getSocialSecurityNumber
60// set gross sales amount
61public void setGrossSales( double sales )
62{
63grossSales = ( sales < 0.0 ) ? 0.0 : sales;
64} // end method setGrossSales
65
66// return gross sales amount
67public double getGrossSales()
68{
69return grossSales;
70} // end method getGrossSales
72// set commission rate
73public 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
79public double getCommissionRate()
80{
81return commissionRate;
82} // end method getCommissionRate
84// calculate earnings
85public double earnings()
86{
87return commissionRate * grossSales;
88} // end method earnings
89
90// return String representation of CommissionEmployee2 object
91public String toString()
92{
93return String.format( "%s: %s %s\n%s: %s\n%s: %.2f\n%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.
[Page 436]
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 |
|
|
|
|
5public class BasePlusCommissionEmployee3 extends CommissionEmployee2
6{
7 |
private double baseSalary; // base salary per week |
8 |
|
9// six-argument constructor
10public BasePlusCommissionEmployee3( String first, String last,
11String ssn, double sales, double rate, double salary )
12{
13super( first, last, ssn, sales, rate );
14setBaseSalary( salary ); // validate and store base salary
15} // end six-argument BasePlusCommissionEmployee3 constructor
17// set base salary
18public void setBaseSalary( double salary )
19{
20baseSalary = ( salary < 0.0 ) ? 0.0 : salary;
21} // end method setBaseSalary
22
23// return base salary
24public double getBaseSalary()
25{
26return baseSalary;
27} // end method getBaseSalary
29// calculate earnings
30public double earnings()
31{
32return baseSalary + ( commissionRate * grossSales );
33} // end method earnings
34
35// return String representation of BasePlusCommissionEmployee3
36public String toString()
37{
38return String.format(
39"%s: %s %s\n%s: %s\n%s: %.2f\n%s: %.2f\n%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.
[Page 437]
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.
[Page 438]
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
4public class BasePlusCommissionEmployeeTest3
5{
6 public static void main( String args[] )
7{
8// instantiate BasePlusCommissionEmployee3 object
9BasePlusCommissionEmployee3 employee =
10new BasePlusCommissionEmployee3(
11"Bob", "Lewis", "333-33-3333", 5000, .04, 300 );
13// get base-salaried commission employee data
14System.out.println(
15"Employee information obtained by get methods: \n" );
16System.out.printf( "%s %s\n", "First name is",
17employee.getFirstName() );
18System.out.printf( "%s %s\n", "Last name is",
19employee.getLastName() );
20System.out.printf( "%s %s\n", "Social security number is",
21employee.getSocialSecurityNumber() );
22System.out.printf( "%s %.2f\n", "Gross sales is",
23employee.getGrossSales() );
24System.out.printf( "%s %.2f\n", "Commission rate is",
25employee.getCommissionRate() );
26System.out.printf( "%s %.2f\n", "Base salary is",
27employee.getBaseSalary() );
28
29 employee.setBaseSalary( 1000 ); // set base salary 30
31System.out.printf( "\n%s:\n\n%s\n",
32"Updated employee information obtained by toString",
33employee.toString() );
34} // end main
35} // end class BasePlusCommissionEmployeeTest3
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.
[Page 441]
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 |
|
|
4public class CommissionEmployee3
5{
6private String firstName;
7private String lastName;
8private String socialSecurityNumber;
9private double grossSales; // gross weekly sales
10private double commissionRate; // commission percentage
12// five-argument constructor
13public CommissionEmployee3( String first, String last, String ssn,
14double sales, double rate )
15{
16// implicit call to Object constructor occurs here
17firstName = first;
18lastName = last;
19socialSecurityNumber = ssn;
20setGrossSales( sales ); // validate and store gross sales
21setCommissionRate( rate ); // validate and store commission rate
22} // end five-argument CommissionEmployee3 constructor
23
24// set first name
25public void setFirstName( String first )
26{
27firstName = first;
28} // end method setFirstName
30// return first name
31public String getFirstName()
32{
33return firstName;
34} // end method getFirstName
36// set last name
37public void setLastName( String last )
38{
39lastName = last;
40} // end method setLastName
42// return last name
43public String getLastName()
44{
45return lastName;
46} // end method getLastName
48// set social security number
49public void setSocialSecurityNumber( String ssn )
50{
51socialSecurityNumber = ssn; // should validate
52} // end method setSocialSecurityNumber
53
54// return social security number
55public String getSocialSecurityNumber()
56{
57return socialSecurityNumber;
58} // end method getSocialSecurityNumber
60// set gross sales amount
61public void setGrossSales( double sales )
62{
63grossSales = ( sales < 0.0 ) ? 0.0 : sales;
64} // end method setGrossSales
65
66// return gross sales amount
67public double getGrossSales()
68{
69return grossSales;
70} // end method getGrossSales
72// set commission rate
73public 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
79public double getCommissionRate()
80{
81return commissionRate;
82} // end method getCommissionRate
84// calculate earnings
85public double earnings()
86{
87return getCommissionRate() * getGrossSales();
88} // end method earnings
89
90// return String representation of CommissionEmployee3 object
91public String toString()
92{
93return String.format( "%s: %s %s\n%s: %s\n%s: %.2f\n%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
