6.1 Single Implementation Inheritance

One of the fundamental mechanisms for code reuse in OOP, is inheritance. It allows new classes to be derived from an existing class. The new class (a.k.a. subclass, subtype, derived class, child class) can inherit members from the old class (a.k.a. superclass, supertype, base class, parent class). The subclass can add new behavior and properties, and under certain circumstances, modify its inherited behavior.

In Java, implementation inheritance is achieved by extending classes (i.e., adding new fields and methods) and modifying inherited members (see Section 6.2, p. 233). Inheritance of members is closely tied to their declared accessibility. If a superclass member is accessible by its simple name in the subclass (without the use of any extra syntax like super), then that member is considered inherited. This means that private, overridden, and hidden members of the superclass are not inherited (see Section 6.2, p. 233). Inheritance should not be confused with the existence of such members in the state of a subclass object (see Example 6.1).

The superclass is specified using the extends clause in the header of the subclass declaration. The subclass only specifies the additional new and modified members in its class body. The rest of its declaration is made up of its inherited members. If no extends clause is specified in the header of a class declaration, then the class implicitly inherits from the java.lang.Object class. This implicit inheritance is assumed in the declaration of the Light class at (1) in Example 6.1. Also in Example 6.1, the subclass TubeLight at (2) explicitly uses the extends clause and only specifies additional members to what it already inherits from the superclass Light (which, in turn, inherits from the Object class). Members of the superclass Light that are accessible by their simple names in the subclass TubeLight, are inherited by the subclass.

Private members of the superclass are not inherited by the subclass and can only be indirectly accessed. The private field indicator of the superclass Light is not inherited, but exists in the subclass object and is indirectly accessible.

Using appropriate accessibility modifiers, the superclass can limit which members can be accessed directly and, thereby, inherited by its subclasses (see Section 4.9, p. 137). As shown in Example 6.1, the subclass can use the inherited members as if they were declared in its own class body. This is not the case for members that are declared private in the superclass. Members that have package accessibility in the superclass are also not inherited by subclasses in other packages, as these members are only accessible by their simple names in subclasses within the same package as the superclass.

Since constructors (see Section 6.3, p. 243) and initializer blocks (see Section 8.2, p. 331) are not members of a class, they are not inherited by a subclass.

Example 6.1 Extending Classes: Inheritance and Accessibility
class Light {                       // (1)
    // Instance fields
              int     noOfWatts;    // wattage
    private   boolean indicator;    // on or off
    protected String  location;     // placement

    // Static fields
    private static int counter;     // no. of Light objects created

    // Constructor
    Light() {
        noOfWatts = 50;
        indicator = true;
        location  = "X";
        counter++;
    }

    // Instance methods
    public  void    switchOn()  { indicator = true; }
    public  void    switchOff() { indicator = false; }
    public  boolean isOn()      { return indicator; }
    private void    printLocation() {
         System.out.println("Location: " + location);
    }

    // Static methods
    public static void writeCount() {
         System.out.println("Number of lights: " + counter);
    }
    //...
}

class TubeLight extends Light {     // (2) Subclass uses the extends clause.
    // Instance fields
    private int tubeLength = 54;
    private int colorNo    = 10;

    // Instance methods
    public int getTubeLength() { return tubeLength; }

    public void printInfo() {
        System.out.println("Tube length: "  + getTubeLength());
        System.out.println("Color number: " + colorNo);
        System.out.println("Wattage: "      + noOfWatts); // Inherited.
    //  System.out.println("Indicator: "    + indicator); // Not Inherited.
        System.out.println("Indicator: "    + isOn());    // Inherited.
        System.out.println("Location: "     + location);  // Inherited.
    //  printLocation();                                  // Not Inherited.
    //  System.out.println("Counter: "    + counter);     // Not Inherited.
        writeCount();                                     // Inherited.
    }
    // ...
}

public class Utility {               // (3)
    public static void main(String[] args) {
       new TubeLight().printInfo();
    }
}

Output from the program:

Tube length: 54
Color number: 10
Wattage: 50
Indicator: true
Location: X
Number of lights: 1

A class in Java can only extend one other class; that is, it can only have one immediate superclass. This kind of inheritance is sometimes called single or linear implementation inheritance. The name is appropriate, as the subclass inherits the implementations of its superclass members. The inheritance relationship can be depicted as an inheritance hierarchy (also called class hierarchy). Classes higher up in the hierarchy are more generalized, as they abstract the class behavior. Classes lower down in the hierarchy are more specialized, as they customize the inherited behavior by additional properties and behavior. Figure 6.1 illustrates the inheritance relationship between the class Light, which represents the more general abstraction, and its more specialized subclasses. The java.lang.Object class is always at the top of any Java inheritance hierarchy, as all classes, with the exception of the Object class itself, inherit (either directly or indirectly) from this class.

Figure 6.1. Inheritance Hierarchy

graphics/06fig01.gif

Inheritance defines the relationship is-a (also called the superclass?subclass relationship) between a superclass and its subclasses. This means that an object of a subclass can be used wherever an object of the superclass can be used. This is often employed as a litmus test for using inheritance. It has particular consequences on how objects can be used. An object of the TubeLight class can be used wherever an object of the superclass Light can be used. An object of the TubeLight class is-an object of the superclass Light. The inheritance relationship is transitive: if class B extends class A, then a class C, which extends class B, will also inherit from class A via class B. An object of the SpotLightBulb class is-a object of the class Light. The is-a relationship does not hold between peer classes: an object of the LightBulb class is not an object of the class TubeLight and vice versa.

Whereas inheritance defines the relationship is-a between a superclass and its subclasses, aggregation defines the relationship has-a (a.k.a. whole?part relationship) between an instance of a class and its constituents (a.k.a. parts). An instance of class Light has the following parts: a field to store its wattage (noOfWatts), a field to store whether it is on or off (indicator), and a String object to store its location (denoted by the field reference location). In Java, a composite object cannot contain other objects. It can only have references to its constituent objects. This relationship defines an aggregation hierarchy that embodies the has-a relationship. Constituent objects can be shared between objects and their lifetimes can be independent of the lifetime of the composite object. Inheritance and aggregation are compared in Section 6.8.

Object-oriented Programming Concepts

The example in this section illustrates basic OOP concepts, and subsequent sections in this chapter will elaborate on the concepts introduced here.

Figure 6.2 shows the inheritance relationship between the class String and its superclass Object. A client that uses a String object is defined in Example 6.2. During the execution of the main() method, the String object created at (1) is denoted by two references: stringRef of the subclass String and objRef of the superclass Object. Walking through the code for the main() method reveals salient features of OOP.

Example 6.2 Illustrating Inheritance
// String class is a subclass of Object class
class Client {
    public static void main(String[] args) {

        String stringRef = new String("Java");                    // (1)

        System.out.println("(2): " + stringRef.getClass());       // (2)
        System.out.println("(3): " + stringRef.length());         // (3)
        Object objRef = stringRef;                                // (4)
   //   System.out.println("(5): " + objRef.length());            // (5) Not OK.
        System.out.println("(6): " + objRef.equals("Java"));      // (6)
        System.out.println("(7): " + objRef.getClass());          // (7)

        stringRef = (String) objRef;                              // (8)
        System.out.println("(9): " + stringRef.equals("C++"));    // (9)
    }
}

Output from the program:

(2): class java.lang.String
(3): 4
(6): true
(7): class java.lang.String
(9): false
Figure 6.2. Inheritance Relationship between String and Object Classes

graphics/06fig02.gif

Inheriting from the Superclass

The subclass String inherits the method getClass() from the superclass Object. A client of the String class can directly invoke this inherited method on objects of the String class in the same way as if the method had been defined in the String class itself. In Example 6.2, this is illustrated at (2).

System.out.println("(2): " + stringRef.getClass());     // (2)
Extending the Superclass

The subclass String defines the method length(), which is not in the superclass Object, thereby extending the superclass. In Example 6.2, invocation of this new method on an object of class String is shown at (3).

System.out.println("(3): " + stringRef.length());       // (3)
Upcasting

A subclass reference can be assigned to a superclass reference because a subclass object can be used where a superclass object can be used. This is called upcasting, as references are assigned up the inheritance hierarchy (see Section 6.6, p. 260). In Example 6.2, this is illustrated at (4), where the value of the subclass reference stringRef is assigned to the superclass reference objRef.

Object objRef = stringRef;                              // (4)

Both references denote the same String object after the assignment. One might be tempted to invoke methods exclusive to the String subclass via the superclass reference objRef, as illustrated at (5).

System.out.println("(5): " + objRef.length());          // (5) Not OK.

However, this will not work as the compiler does not know what object the reference objRef is denoting. It only knows the class of the reference. As the declaration of the Object class does not have a method called length(), this invocation of length() at (5) would be flagged as a compile-time error.

Method Overriding

In contrast to the situation at (5), the invocation of the equals() method at (6) using the superclass reference objRef is legal because the compiler can check that the Object class does define a method named equals.

System.out.println("(6): " + objRef.equals("Java"));    // (6)

Note that this method is redefined in the String class with the same signature (i.e., method name and parameters) and the same return type. This is called method overriding (see Section 6.2, p. 233).

Polymorphism and Dynamic Method Binding

The invocation of the equals() method at (6), using the superclass reference objRef, does not necessarily invoke the equals() method from the Object class at runtime. The method invoked is dependent on the type of the actual object denoted by the reference at runtime. The actual method is determined by dynamic method lookup. The ability of a superclass reference to denote objects of its own class and its subclasses at runtime is called polymorphism. Section 6.7 provides a discussion on how polymorphism and dynamic method lookup can be employed to achieve code reuse.

Under normal program execution, the reference objRef will refer to an object of the String class at (6), resulting in the equals() method from the String class being executed, and not the one in the Object class.

The situation at (7), where the getClass() method is invoked using the superclass reference objRef, is allowed at compile time because the Object class defines a method named getClass.

System.out.println("(7): " + objRef.getClass());        // (7)

In this case, under normal program execution, the reference objRef will refer to an object of the String class at (7). Dynamic method lookup determines which method implementation binds to the method signature getClass(). Since no getClass() method is defined in the String class, the method getClass() inherited from the Object class is thus executed.

Downcasting

Casting the value of a superclass reference to a subclass type is called downcasting (see Section 6.6, p. 260). This is illustrated in Example 6.2 by assigning references down the inheritance hierarchy, which requires explicit casting.

stringRef = (String) objRef;                            // (8)
System.out.println("(9): " + stringRef.equals("C++"));  // (9)

At (8), the source reference objRef is of type Object, which is the superclass of the class of the destination reference stringRef. If the reference objRef actually denoted an object of class String at runtime, the cast would convert it to the proper subclass type, so that the assignment to the reference stringRef would be legal at (8). The reference stringRef could then be used to invoke the equals() method on this String object, as at (9). Not surprisingly, the equals() method from the String class would be executed.

The compiler verifies that an inheritance relationship exists between the source reference type and the reference type specified in the cast. However, the cast can be invalid at runtime. If, at runtime, the reference objRef denotes an object of class Object or some unrelated subclass of class Object, then obviously casting the reference value to that of subclass String would be illegal. In such a case, a ClassCastException would be thrown at runtime. The instanceof operator (see Section 6.6, p. 264) can be used to determine the runtime type of an object before any cast is applied.