9. Object-Oriented Programming: Inheritance

Objectives

In this chapter you’ll learn:

• How inheritance promotes software reusability.

• The notions of superclasses and subclasses.

• To use keyword extends to create a class that inherits attributes and behaviors from another class.

• To use access modifier protected to give subclass methods access to superclass members.

• To access superclass members with super.

• How constructors are used in inheritance hierarchies.

• The methods of class Object, the direct or indirect superclass of all classes in Java.

Say not you know another entirely, till you have divided an inheritance with him.

Johann Kasper Lavater

This method is to define as the number of a class the class of all classes similar to the given class.

Bertrand Russell

Good as it is to inherit a library, it is better to collect one.

Augustine Birrell

Save base authority from others’ books.

William Shakespeare

Outline

9.1   Introduction

9.2   Superclasses and Subclasses

9.3   protected Members

9.4   Relationship between Superclasses and Subclasses

9.4.1   Creating and Using a CommissionEmployee Class

9.4.2   Creating a BasePlusCommissionEmployee Class without Using Inheritance

9.4.3   Creating a CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy

9.4.4   CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy Using protected Instance Variables

9.4.5   CommissionEmployeeBasePlusCommissionEmployee Inheritance Hierarchy Using private Instance Variables

9.5   Constructors in Subclasses

9.6   Software Engineering with Inheritance

9.7   Object Class

9.8   Wrap-Up

9.1 Introduction

This chapter continues our discussion of object-oriented programming (OOP) by introducing one of its primary features—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, programmers save time during program development by reusing proven and debugged high-quality software. This also increases the likelihood that a system will be implemented effectively.

When creating a class, rather than declaring completely new members, you can designate that the new class should inherit the members of an existing class. The existing class is called the superclass, and the new class is the subclass. (The C++ programming language refers to the superclass as the base class and the subclass as the derived class.) Each subclass can become the superclass for future subclasses.

A subclass normally adds its own fields and methods. Therefore, a subclass is more specific than its superclass and represents a more specialized group of objects. Typically, the subclass exhibits the behaviors of its superclass and additional behaviors that are specific to 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 9.7 lists the methods of class Object, which every other class inherits. In the case of single inheritance, a class is derived from one direct superclass. Java, unlike C++, does not support multiple inheritance (which occurs when a class is derived from more than one direct superclass). In Chapter 10, Object-Oriented Programming: Polymorphism, we explain how Java programmers can use interfaces to realize many of the benefits of multiple inheritance while avoiding the associated problems.

Experience in building software systems indicates that significant amounts of code deal with closely related special cases. When you are preoccupied with special cases, the details can obscure the big picture. With object-oriented programming, you focus on the commonalities among objects in the system rather than on the special cases.

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. For example, a car is a vehicle. By contrast, has-a represents composition (see Chapter 8). In a has-a relationship, an object contains as members references to other objects. For example, a car has a steering wheel (and a car object has a reference to a steering wheel object).

New classes can inherit from classes in class libraries. Organizations develop their own class libraries and can take advantage of others available worldwide. Some day, most new software likely will be constructed from standardized reusable components, just as automobiles and most computer hardware are constructed today. This will facilitate the development of more powerful, abundant and economical software.

9.2 Superclasses and Subclasses

Often, an object of one class is an object of another class as well. For example, in geometry, a rectangle is a quadrilateral (as are squares, parallelograms and trapezoids). Thus, in Java, class Rectangle can be said to inherit from class Quadrilateral. In this context, class Quadrilateral is a superclass and class Rectangle is a subclass. A rectangle is a specific type of quadrilateral, but it is incorrect to claim that every quadrilateral is a rectangle—the quadrilateral could be a parallelogram or some other shape. Figure 9.1 lists several simple examples of superclasses and subclasses—note that superclasses tend to be “more general” and subclasses “more specific.”

Fig. 9.1. Inheritance examples.

Image

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 typically larger than the set of objects represented by any of its subclasses. For example, the superclass Vehicle rep-resents all vehicles, including cars, trucks, boats, bicycles and so on. By contrast, subclass Car represents a smaller, more specific subset of vehicles.

Inheritance relationships form tree-like hierarchical structures. A superclass exists in a hierarchical relationship with its subclasses. When classes participate in inheritance relationships, they become “affiliated” with other classes. A class becomes either a superclass, supplying members to other classes, or a subclass, inheriting its members from other classes. In some cases, a class is both a superclass and a subclass.

Let’s develop a sample class hierarchy (Fig. 9.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. Faculty members are either administrators (e.g., deans and department chairpersons) or teachers. Note that 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.

Fig. 9.2. Inheritance hierarchy for university CommunityMembers.

Image

Each arrow in the hierarchy represents an is-a relationship. As we follow the arrows 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 of the diagram, the reader 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 and is a CommunityMember.

Now consider the Shape inheritance hierarchy in Fig. 9.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 some more specific types of TwoDimensionalShapes and ThreeDimensionalShapes. As in Fig. 9.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. Note that this hierarchy could contain many other classes. For example, ellipses and trapezoids are TwoDimensionalShapes.

Fig. 9.3. Inheritance hierarchy for Shapes.

Image

Not every class relationship is an inheritance relationship. In Chapter 8, we discussed the has-a relationship, in which classes have members that are references to objects of other classes. Such relationships create classes by composition of existing classes. For example, given the classes Employee, BirthDate and TelephoneNumber, it is improper to say that an Employee is a BirthDate or that an Employee is a TelephoneNumber. However, an Employee has a BirthDate, and an Employee has a TelephoneNumber.

It is possible to treat superclass objects and subclass objects similarly—their commonalities are expressed in the members of the superclass. Objects of all classes that extend a common superclass can be treated as objects of that superclass (i.e., such objects have an is a relationship with the superclass). However, superclass objects cannot be treated as objects of their subclasses. For example, all cars are vehicles, but not all vehicles are cars (the other vehicles could be trucks, planes or bicycles, for example). Later in this chapter and in Chapter 10, Object-Oriented Programming: Polymorphism, we consider many examples that take advantage of the is-a relationship.

One problem with inheritance is that a subclass can inherit methods that it does not need or should not have. Even when a superclass method is appropriate for a subclass, that subclass often needs a customized version of the method. In such cases, the subclass can override (redefine) the superclass method with an appropriate implementation, as we’ll see often in the chapter’s code examples.

9.3 protected Members

Chapter 8 discussed access modifiers public and private. A class’s public members are accessible wherever the program has a reference to an object of that class or one of its subclasses. A class’s private members are accessible only from within the class itself. A super-class’s private members are not inherited by its subclasses. 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 members of that superclass, by members of its subclasses and by members of other classes in the same package (i.e., protected members also have package access).

All public and protected superclass members retain their original access modifier when they become members of the subclass (i.e., public members of the superclass become public members of the subclass, and protected members of the superclass become protected members of the subclass).

Subclass methods can refer to public and protected members inherited from the superclass simply by using the member names. When a subclass method overrides a super-class 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 9.4.

Software Engineering Observation 9.1

Image

Methods of a subclass cannot directly access private members of their superclass. A subclass can change the state of private superclass instance variables only through non-private methods provided in the superclass and inherited by the subclass.

Software Engineering Observation 9.2

Image

Declaring private instance variables helps programmers test, debug and correctly modify systems. If a subclass could access its superclass’s private instance variables, classes that inherit from that subclass could access the instance variables as well. This would propagate access to what should be private instance variables, and the benefits of information hiding would be lost.

9.4 Relationship between Superclasses and Subclasses

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 requires—we’ll 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 members—this 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 Object—class 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 code—we do so in this example only for demonstration purposes.

Fig. 9.4. CommissionEmployee class represents an employee paid a percentage of gross sales.

Image

Image

Image

Image

Software Engineering Observation 9.3

Image

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 13–22) and methods earnings (lines 85–88) and toString (lines 91–98). Lines 25–82 declare public get and set methods for manipulating the class’s instance variables (declared in lines 6–10) 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 super-class’s default or no-argument constructor.

After the implicit call to Object’s constructor occurs, lines 17–21 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. We could validate the first and last names—perhaps 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 85–88) calculates a CommissionEmployee’s earnings. Line 87 multiplies the commissionRate by the grossSales and returns the result.

Method toString (lines 91–98) is special—it 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. 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.

Common Programming Error 9.1

Image

It is 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 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 9–10 instantiate a CommissionEmployee object and invoke CommissionEmployee’s constructor (lines 13–22 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 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. 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.

Fig. 9.5. CommissionEmployee class test program.

Image

Image

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 15–25) and methods earnings (lines 100–103) and toString (lines 106–114). Lines 28–97 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. 9.4)—in this example, we’ll not yet exploit that similarity.

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

Image

Image

Image

Image

Note that class BasePlusCommissionEmployee does not specify “extends Object” in line 5, so the class implicitly extends Object. Also note that, like class CommissionEmployee’s constructor (lines 13–22 of Fig. 9.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 100–103) 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 9–11 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 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. 9.6, lines 88–91) ensures that instance variable baseSalary is not assigned a negative value, because an employee’s base salary cannot be negative. Lines 31–33 of Fig. 9.7 invoke the object’s toString method explicitly to get the object’s string representation.

Fig. 9.7. BasePlusCommissionEmployee test program.

Image

Image

Much of class BasePlusCommissionEmployee’s code (Fig. 9.6) is similar, if not identical, to that of 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 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

Image

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 or, in some cases, composition, rather than the “copy-and-paste” approach, in situations where you want one class to “absorb” the instance variables and methods of another class.

Software Engineering Observation 9.5

Image

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 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.

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 9–16), public methods inherited from class CommissionEmployee, method setBaseSalary (lines 19–22), method getBaseSalary (lines 25–28), method earnings (lines 31–35) and method toString (lines 38–47).

Fig. 9.8. private superclass members cannot be accessed in a subclass.

Image

Image

Image

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 9–16) 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 13–22 of Fig. 9.4) 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 BasePlusCommissionEmployee2’s constructor did not invoke CommissionEmployee’s constructor explicitly, Java would attempt to invoke class CommissionEmployee’s no-argument or default constructor—but the class does not have such a constructor, so the compiler would issue an error. The explicit superclass constructor call in line 13 of Fig. 9.8 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

Image

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 private—subclass BasePlusCommissionEmployee2’s methods are not allowed to access superclass CommissionEmployee’s private instance variables. Note that we used bold black text in Fig. 9.8 to indicate erroneous code. The compiler issues additional errors at lines 43–45 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 43–45 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 super-class. 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 6–10) 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.

Fig. 9.9. CommissionEmployee2 with protected instance variables.

Image

Image

Image

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 commissionRate—all 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 40–42 of method toString. If another class extends BasePlusCommissionEmployee3, the new subclass also inherits the protected members.

Fig. 9.10. BasePlusCommissionEmployee3 inherits protected instance variables from CommissionEmployee2.

Image

Image

Class BasePlusCommissionEmployee3 does not inherit class CommissionEmployee2’s constructor. However, class BasePlusCommissionEmployee3’s six-argument constructor (lines 10–15) 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.

Fig. 9.11. protected superclass members inherited into subclass BasePlusCommissionEmployee3.

Image

Image

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 super-class 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 members—this is not always desirable.

Software Engineering Observation 9.6

Image

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

Image

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

Image

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 6–10) 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 85–88) and toString (lines 91–98) 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. Note that 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. Subclass BasePlusCommissionEmployee4 (Fig. 9.13) inherits CommissionEmployee3’s non-private methods and can access the private superclass members via those methods.

Fig. 9.12. CommissionEmployee3 class uses methods to manipulate its private instance variables.

Image

Image

Image

Fig. 9.13. BasePlusCommissionEmployee4 class extends CommissionEmployee3, which provides only private instance variables.

Image

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 31–34) and toString (lines 37–41) 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 31–34) overrides class CommissionEmployee3’s earnings method (Fig. 9.12, lines 85–88) 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 subclass—place 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

Image

When a superclass method is overridden in a subclass, the subclass version often calls the super-class 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, potentially creating an error called infinite recursion.

Similarly, BasePlusCommissionEmployee4’s toString method (Fig. 9.13, lines 37–41) overrides class CommissionEmployee3’s toString method (Fig. 9.12, lines 91–98) 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.

Fig. 9.14. Superclass private instance variables are accessible to a subclass via public or protected methods inherited by the subclass.

Image

Image

In this section, you saw an evolutionary set of examples that was 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.

9.5 Constructors in Subclasses

As we explained in the preceding section, instantiating a subclass object begins a chain of constructor calls in which the subclass constructor, before performing its own tasks, invokes its direct superclass’s constructor either explicitly (via the super reference) or implicitly (calling the superclass’s default constructor or no-argument constructor). Similarly, if the super-class is derived from another class (as is, of course, every class except Object), the superclass constructor invokes the constructor of the next class up in the hierarchy, and so on. The last constructor called in the chain is always the constructor for class Object. The original subclass constructor’s body finishes executing last. Each superclass’s constructor manipulates the superclass instance variables that the subclass object inherits. For example, consider again the CommissionEmployee3BasePlusCommissionEmployee4 hierarchy from Fig. 9.12 and Fig. 9.13. When a program creates a BasePlusCommissionEmployee4 object, the BasePlusCommissionEmployee4 constructor is called. That constructor calls CommissionEmployee3’s constructor, which in turn calls Object’s constructor. Class Object’s constructor has an empty body, so it immediately returns control to CommissionEmployee3’s constructor, which then initializes the private instance variables of CommissionEmployee3 that are part of the BasePlusCommissionEmployee4 object. When CommissionEmployee3’s constructor completes execution, it returns control to BasePlusCommissionEmployee4’s constructor, which initializes the BasePlusCommissionEmployee4 object’s baseSalary.

Software Engineering Observation 9.8

Image

When a program creates a subclass object, the subclass constructor immediately calls the superclass constructor (explicitly, via super, or implicitly). The superclass constructor’s body executes to initialize the superclass’s instance variables that are part of the subclass object, then the subclass constructor’s body executes to initialize the subclass-only instance variables. Java ensures that even if a constructor does not assign a value to an instance variable, the variable is still initialized to its default value (e.g., 0 for primitive numeric types, false for booleans, null for references).

Our next example revisits the commission employee hierarchy by declaring a CommissionEmployee4 class (Fig. 9.15) and a BasePlusCommissionEmployee5 class (Fig. 9.16). Each class’s constructor prints a message when invoked, enabling us to observe the order in which the constructors in the hierarchy execute.

Fig. 9.15. CommissionEmployee4’s constructor outputs text.

Image

Image

Image

Fig. 9.16. BasePlusCommissionEmployee5’s constructor outputs text.

Image

Class CommissionEmployee4 (Fig. 9.15) contains the same features as the version of the class shown in Fig. 9.4. We modified the constructor (lines 13–25) to output text upon its invocation. Note that outputting this with the %s format specifier (lines 23–24) implicitly invokes the toString method of the object being constructed to obtain the object’s string representation.

Class BasePlusCommissionEmployee5 (Fig. 9.16) is almost identical to BasePlusCommissionEmployee4 (Fig. 9.13), except that BasePlusCommissionEmployee5’s constructor also outputs text when invoked. As in CommissionEmployee4 (Fig. 9.15), we output this using the %s format specifier (line 16) to get the object’s string representation.

Figure 9.17 demonstrates the order in which constructors are called for objects of classes that are part of an inheritance hierarchy. Method main begins by instantiating CommissionEmployee4 object employee1 (lines 8–9). Next, lines 12–14 instantiate BasePlusCommissionEmployee5 object employee2. This invokes the CommissionEmployee4 constructor, which prints output with the values passed from the BasePlusCommissionEmployee5 constructor, then performs the output specified in the BasePlusCommissionEmployee5 constructor. Lines 17–19 then instantiate BasePlusCommissionEmployee5 object employee3. Again, the CommissionEmployee4 and BasePlusCommissionEmployee5 constructors are both called. In each case, the body of the CommissionEmployee4 constructor executes before the body of the BasePlusCommissionEmployee5 constructor executes. Note that employee2 is constructed completely before construction of employee3 begins.

Fig. 9.17. Constructor call order.

Image

Image

9.6 Software Engineering with Inheritance

When a new class extends an existing class, the new class inherits the non-private members of the existing class. We can customize the new class to meet our needs by including additional members and by overriding superclass members. Doing this does not require the subclass programmer to change the superclass’s source code. Java simply requires access to the superclass’s .class file so it can compile and execute any program that uses or extends the superclass. This powerful capability is attractive to independent software vendors (ISVs), who can develop proprietary classes for sale or license and make them available to users in bytecode format. Users then can derive new classes from these library classes rapidly and without accessing the ISVs’ proprietary source code.

Software Engineering Observation 9.9

Image

Although inheriting from a class does not require access to its source code, developers often insist on seeing the source code to understand the class’s is implementation. Developers in industry want to ensure that they are extending a solid class that performs well and is implemented securely.

People experienced with large-scale software projects in industry say that effective software reuse improves the software-development process. Object-oriented programming facilitates software reuse, potentially shortening development time.

The availability of substantial and useful class libraries delivers the maximum benefits of software reuse through inheritance. Application designers build their applications with these libraries, and library designers are rewarded by having their libraries included with the applications. The standard Java class libraries that are shipped with Java SE 6 tend to be rather general purpose. Many special-purpose class libraries exist and more are being created.

Software Engineering Observation 9.10

Image

In an object-oriented system, the designer often finds closely related classes, then “factors out” common instance variables and methods into a superclass. The designer uses inheritance to develop subclasses, specializing them with capabilities beyond those inherited from the superclass.

Software Engineering Observation 9.11

Image

Declaring a subclass does not affect its superclass’s source code. Inheritance preserves the integrity of the superclass.

Software Engineering Observation 9.12

Image

Just as designers of non-object-oriented systems should avoid method proliferation, designers of object-oriented systems should avoid class proliferation. Such proliferation creates management problems and can hinder software reusability, because in a huge class library it becomes difficult for a client to locate the most appropriate classes. The alternative is to create fewer classes that provide more substantial functionality, but such classes might prove cumbersome.

Reading subclass declarations can be confusing, because inherited members are not declared explicitly in the subclasses, but are nevertheless present in them. A similar problem exists in documenting subclass members.

9.7 Object Class

All Java classes inherit directly or indirectly from class Object (package java.lang), so its 11 methods are inherited by all other classes. Figure 9.18 summarizes Object’s methods.

Fig. 9.18. Object methods that are inherited directly or indirectly by all classes.

Image

Image

Image

You can learn more about Object’s methods in the online API documentation and in The Java Tutorial at java.sun.com/javase/6/docs/api/java/lang/Object.html and java.sun.com/docs/books/tutorial/java/IandI/objectclass.html, respectively.

Recall from Chapter 7 that arrays are objects. As a result, an array inherits the members of class Object. Every array has an overridden clone method that copies the array, but if the array stores references to objects, the objects are not copied. You can learn more about the relationship between arrays and class Object in the Java Language Specification, Chapter 10, at java.sun.com/docs/books/jls/second_edition/html/arrays.doc.html.

9.8 Wrap-Up

This chapter introduced inheritance—the ability to create classes by absorbing an existing class’s members and embellishing them with new capabilities. You learned the notions of superclasses and subclasses and used keyword extends to create a subclass that inherits members from a superclass. The chapter introduced the access modifier protected; subclass methods can access protected superclass members. You learned how to access super-class members with super. You also saw how constructors are used in inheritance hierarchies. Finally, you learned about the methods of class Object, the direct or indirect superclass of all classes in Java.

In Chapter 10, Object-Oriented Programming: Polymorphism, we build on our discussion of inheritance by introducing polymorphism—an object-oriented concept that enables us to write programs that conveniently handle, in a more general manner, objects of a wide variety of classes related by inheritance. After studying Chapter 10, you’ll be familiar with classes, objects, encapsulation, inheritance and polymorphism—the key technologies of object-oriented programming.

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

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