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
(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
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 Commission-Employee
’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.
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
.
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.