Inheritance

Inheritance allows a programmer to create a hierarchy of classes.  General classes can define traits common to a set of related items.  Classes that inherit the general class can define more specific objects, adding things that are unique to them.

Contents

The Extends keyword

The extends keyword allows a subclass to reference a superclass’ fields and methods.

class A {
    int i;                     // defaults to 0                   

    void foo() {
        System.out.println(i);
    }
}
class B extends A {
    int j;                    // defaults to 0                   

    void bar() {
        System.out.println(i + j);
    }
}
/* Driver */
B obj = new B();
obj.i = 10;
obj.j = 20;
obj.foo();                    // prints 10
obj.bar();                    // prints 30

Reasons for Inheritance

Inheritance is useful for a number of reasons. Some of them include:

  • Code reuse
  • Better API design
  • Easier maintenance

The Final Keyword

A class that is declared final can not be extended.

A method that is declared final can not be overidden.

A field that is declared final can not be modified after construction.

Member Access

If a superclass and a subclass reside in the same package, the subclass has access to members in the superclass that

  • are public or protected
  • have no modifier at all.

If a superclass and a subclass reside in different packages, the subclass has access to members in A that

  • are public or protected

For the rest of this chapter we’ll assume both classes, A and B, reside in the same package.

Super and Subclass Constructors

The extends keyword allows us to create a hierarchy of class.  If class C extends class B, and class B extends class A, then we say that class A is the most general class in the hierarchy and class C is the most specific class in the hierarchy.

When an instance of a class is created, a constructor for each of the classes in its class hierarchy is executed.  They are executed in the order from most general to most specific according to the following rules:

  • If a subclass’ constructor does not explicitly call a superclass’ constructor, the superclass’ parameterless constructor in the superclass is executed.  If the superclass does not have a parameterless constructor, a compile error will occur.
  • If a superclass has constructors, but they all have parameters, a subclass must call one of them explicitly using super(…).  This call must be the first statement in the subclass’ constructor(s).

In the example below, even though B’s constructor does not explicitly call A’s constructor, A’s parameterless constructor is executed before B’s constructor.

class A {
    int i = 0;

    A() {
        this.i = 10;
    }
}
class B extends A {
    int j = 0;

    B(int j) {
        this.j = j;
    }

    void foo() {
        System.out.println(i + j);
    }
}
/* Driver */
B obj = new B(20);
obj.bar();                    // prints 30

If the superclass only has constructors with parameters, the subclass constructor must invoke one of the superclass’ constructors explicitly using the super keyword as the first statement in the subclass’ constructor.

class A {
    int i = 0;

    A(int i) {
        this.i = i;
    }
}
class B extends A {
    int j = 0;

    B(int i, int j) {
        super(i);                     // must include call to super()
        this.j = j;
    }

    void foo() {
        System.out.println(i + j);
    }
}
/* Driver */
B obj = new B(10, 20);
obj.foo();                                // prints 30

Getters and Setters

When designing classes we typically hide fields and methods from subclasses with the use of the private modifier and control access to these resources through the use of accessor functions (setters and getters) and constructors.

class A {
    private int i;

    void seti(int i) {
        this.i = i;
    }

    int geti() {
        return i;
    }
}
class B extends A {
    int j;

    B(int i, int j) {
        seti(i);
        this.j = j;
    }

    void foo() {
        System.out.prinln(geti() + j);
    }
}
/* Driver */
B obj = new B(10,20);
System.out.println(obj.i);                 // Won’t compile - i is private
obj.foo();                                            // prints 30

Overriding Superclass Methods and Redefining Fields

Subclasses may define methods with the same signature as a method in its superclass and redefine variables with the same type and identifier.

If that is the case, when the method is called on an instance of the subclass, the subclass’ method is invoked and when the variable name is used, its subclass variable is used.  A subclass can invoke a superclass’ overridden methods and use the superclass’ redefined variables by using the super keyword.

class A {
    int i = 10;

    void foo() {
        System.out.println(i);
    }
}
class B extends A {
    int i = 20;

    @Override
    void foo() {
        super.foo();
        System.out.prinln(i + super.i);
    }
}
/* Driver */
B obj = new B();
obj.foo();                    // prints 10 and 30

Prohibiting Inheritance and Overriding Methods

We can prevent a class from being inherited by making the class final.

final class A {
    int i = 10;

    void foo () {
        System.out.println(i);
    }
}

We can also prevent a subclass from overriding a method in a superclass by making the method in the superclass final.

class A {
    int i = 10;

    final void foo() {
        System.out.println(i);
    }
}

Dynamic Dispatch

Superclass objects can also reference, at runtime, subclass fields and methods that were first defined in the superclass and overridden in the subclass.  This is the powerful tool called dynamic dispatch.

class A {
    int i;
    int j;

    void foo() {
        System.out.println(i);
    }

    void bar() {
        System.out.println(j);
    }
}
class B extends A {
    int i;                    //overrides i in A
    int k;

    B(int i, int j, int k) {
        this.i = i;          // sets this class' i
        this.j = j;          // sets superclass' j
        this.k = k;          // sets this class' k
    }

    @Override
    void foo() {
        System.out.println(i + j);
    }

    void mow() {
        System.out.println(k);
    }
}
/* Driver */
B obj = new B(10, 20, 30);    // declare instance of B
obj.foo();                    // prints 30
obj.bar();                    // prints 20
obj.mow();                    // prints 30

A obj2 = obj;                 // create superclass reference

obj2.foo();                   // prints 30 - subclass' method is called
obj2.bar();                   // prints 20
obj2.mow();                   // won't compile

Favor Composition Over Inheritance (Item 16)

Bloch recommends that before using inheritance you should ask if your new class has an “is a” relationship with the class you want to extend.  For example, Truck is a Vehicle, so it may be appropriate to have a Truck class extend a Vehicle class.  If there does not seem to be an “is a” relationship between the two classes, you may want to consider composition rather than inheritance.  The reason for this is that subclasses are fragile.

Since subclasses inherit all of the methods of a superclass, if a later version of the superclass included a method whose name had serendipitously already been used by the subclass, then unless the subclass modifies the signature of the method (to overload the method) then the subclass will automatically override the implementation provided by in the superclass.

Similarly, if a superclass adds a method in a later version, the authors of the subclass have no choice but to revisit their code to see if the implementation provided in the superclass creates a bug in the subclass implementation.

To avoid these problems a new class can create a field to hold a reference to instance of the existing class.  If the methods in the existing class need to be accessible in the API for the new class, the new class can create forwarding methods.  It may be time consuming to create these forwarding methods (like using the builder pattern), however your implementation will be much more stable.

Design and document for inheritance else prohibit it. (Item 17)

One must consciously decide whether or not to allow inheritance.   When making that decision one should understand the limitations.  If you do allow a class to be extended its constructors must not call methods that can be overridden.   If so, runtime errors are bound to occur.  Consider the following example:

public class Foo {
    public Foo() {
        bar();
    }
    public void bar() {
        System.out.println(“bar”);
    }
}
public class SubFoo extends Foo {
    String s1 = "hello";
    String s2;

    public SubFoo(String s) {
        this.s2 = s;
    }
    @Override public void bar() {
        System.out.print("bar: " + s2.equals(s1));
    }
}
public class Driver {
    public static void main (String args[]) {
        SubFoo sf = new SubFoo("bye");
    }
}

In this example, on quick glance, one might think that the driver would print “bar: false” when run since “hello” is not equal to “bye”.  In actuality, the program terminates with a NullPointerException runtime error.

The reason for the runtime error can be seen when we trace the execution of the driver.  When we create an instance of SubFoo, Foo’s constructor is called before SubFoo’s constructor.  When Foo’s constructor is called, it executes the method named bar.  Since bar has been overridden in the class SubFoo, SubFoo’s bar method is executed.  SubFoo’s bar method attmpts to execute the equals method on s2, but s2 is null because SubFoo’s constructor, which initializes the field s2, has yet to be executed.  And since s2 is null, we get a NullPointerException when we try to invoke a method on the null value in s2.

A second limitation (or requirement) is that a public or protected method that calls an overridable method in the same class must be documented to include this fact, so that developers that extend the class can weigh whether or not it is appropriate to override the overridable method.

To prohibit a class from being extended we simply need to make the class final by including the final modifier in the class declaration.

final public class Foo {
    ...
}

Prefer class hierarchies to tagged classes (Item 20)

A tagged class is a class that represents different types based on the value of a field.  As an example, consider the following class provided by Bloch.

class Figure {
    enum Shape {RECTANGLE, CIRCLE};
    final Shape shape;

    // CIRCLE fields
    double radius;

    // RECTANGLE fields    
    double length;
    double width;

    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return MATH.PI * (radius * radius);
            default:
                throw new AssertionError();
        }
    }
}

As Bloch suggests, tagged classes are a bad imitation to a hierarchal framework and are verbose, error-prone, and inefficient.

 

 

 

© 2017 – 2019, Eric. All rights reserved.