Inheritance

Goals

Concepts

Language

Javadoc

Library

Lesson

Classes aren't always created from scratch. Java allows for you to make specializations of existing classes. In object-oriented parlance, this is referred to as inheritance—one of the pillars of object-oriented programming (along with encapsulation, which you have already studied). Inheritance attempts to mimic real-world relationships, such as the different types of categories of “vehicles” in the diagram below:

Vehicle hierarchy class diagram.
Vehicle hierarchy class diagram.

Declaration

In Java you can specialize an existing class to create a child or subclass of the existing class. This is done with the extends keyword.

An Animal class with its subclass Dog.
public class Animal
{
}
public class Dog extends Animal
{
}

Inheritance

What is the benefit of this? One of the main motivations is to consolidate common code that would otherwise be repeated. Code that apply to all the classes can be placed in the super class of commonality rather than being duplicated in all the subclasses.

Inheriting a move() method from a Vehicle base class.
public class Vehicle
{
  /** Moves the position of the vehicle. */
  public void move()
  {
    //TODO turn wheels
  }
}
public class Car extends Vehicle
{
}

Now every instance of Vehicle, including Car, will know how to move.

final Vehicle vehicle = new Vehicle();
vehicle.move();

final Car car = new Car();
car.move();

Polymorphism

Along with encapsulation and inheritance, the third central feature of object-oriented programming is polymorphism (which comes from Greek and means “many shapes”). In object-oriented terms, this means that a subclass can change the way a method functions by overriding the version that appears in the base class.

Subclassing a Vehicle class with polymorphism.
public class Vehicle
{
  /** Moves the position of the vehicle. */
  public void move()
  {
    //TODO turn wheels
  }
}
public class Car extends Vehicle
{
  //nothing to do here; inherits move() from Vehicle
}
public class Airplane extends Vehicle
{

  /** {@inheritdoc} This version moves by virtue of propellers instead of wheels. */
  @Override
  public void move()
  {
    //TODO turn propellers
  }
}

There is an important thing to note here: subclasses can change their behavior by overriding classes, and this holds even if the type of the reference is of the super class! Not only can you assign an Airplane to a Vehicle reference variable, calling its move() method will still invoke the Airplane version instead of the Vehicle version.

final Vehicle vehicle = new Airplane();
vehicle.move(); //invoked Airplane.move()

Liskov Substitution Principle

Polymorphism is a powerful concept. It means that a method can take the general, base type as a parameter and know that the correct behavior will occur regardless of which subclass instance is passed. Consider a Raceway class that knows how to race vehicles.

A Raceway class for racing instances of type Vehicle.
public class Raceway
{

  public static void race(final Vehicle... vehicles)
  {
    for(final Vehicle vehicle:vehicles)
    {
      vehicle.move();
    }
  }

}

We can now use the Raceway to race a car against an airplane. The Raceway does not care what type of vehicle is given; it simply calls the move() method of each one, and the Vehicle instance knows how to move based upon its own implementation of move() (or the one it inherited from Vehicle if it did not override it).

Raceway.race(new Car(), new Airplane());

One implication of polymorphism is that, because a caller does not know which subclass method implementation is being invoked, subclasses should design their implementations so that their behavior matches the contract of the base method. The Liskov Substitution Principle says in part that if some logic in the program expects an instance of the base class (such as Vehicle), you should be able to substitute an instance of any subclass (such as Car or Airplane) and the program should continue to function. To make this happen, all the subclasses overrides must continue to match the original method contract, which we discussed in the lesson on contract programming. This means that:

  1. the contract must be written sufficiently general to allow for variations in implementation, while concentrating on the central semantics of the behavior; and
  2. the subclass implementations must be written carefully so that they adhere to the contract of the base method.

Super Methods

Sometimes a subclass doesn't want to replace functionality altogether—it may want merely to add to the existing functionality of a super class. A subclass is therefore allows to invoke the super class version of a function before, during, or after the specialized version. Take for example a “tiller truck”, a type of fire engine that has separate drivers for the front and rear sets of wheels.

Tiller truck fire engine.
Tiller or “hook-and-ladder” truck fire engine. (Wikimedia Commons)

Steering is performed independently, so a TillerTruck may want to add to the default steer() function. The super class version of steer is invoked using super.steer().

A subclass method implementation calling its super implementation.
public class Truck extends Vehicle
{

  /** Turns the trunk left or right. */
  public void steer()
  {
    //TODO steer the main set of wheels
  }

}
public class TillerTruck extends Truck
{

  /** {@inheritDoc} This version additionally steers the back set of wheels. */
  @Override
  public void steer()
  {
    super.steer();  //invoke the super-class version
    //TODO steer the back set of wheels
  }

}

Multiple Inheritance

Let's say that the class Car has a method turnQuickly(), while the class Truck has a method haulStuff(). You decide that a Pickup is sort of part Car, part Truck, and you wish it could inherit aspects of both classes.

Vehicle multiple inheritance class diagram.
Vehicle multiple inheritance class diagram.

This is called multiple inheritance, and some programming languages allow it. Java only allows single inheritance, allowing a class to extend a single class, not multiple classes.

How might you then include common features of Car and Truck in your Pickup class, if you can't use inheritance? You might extract out the common functionality into a separate class, so that Pickup contains an instance of this class; this is called composition, because Pickup is “composed” of this functionality rather than inheriting it.

Abstract Classes

As the inheritance hierarchy for Vehicle grows, the number of specialized classes increases. The Vehicle class at the root of the hierarchy may become so general it serves merely as a base commonality of all specialized vehicles. There would be little use in instantiating such a general Vehicle class instead of a more specific subclass such as Car or Airplane.

Java allows you to declare that a class is “abstract” and therefore must not be instantiated. An abstract class serves as sort of a “placeholder” in the inheritance hierarchy, an abstraction that encapsulates information and functionality to be used by specialized classes. You can make a class abstract by using the abstract keyword in the class declaration.

An abstract vehicle class.
/**
 * Abstract base class for vehicles.
 * Only concrete subclasses can be instantiated.
 */
public abstract class AbstractVehicle
{
}

Construction

Subclasses present special difficulties when it comes to construction and initialization. Sometimes the class being extended has a constructor that initializes the super class state. The constructor of the subclass must invoke some constructor of the super class, and this is done with the same super keyword which is used to access super class method versions, except that in this case super(…) is used as if it were a method.

Subclasses invoking the constructor of a super abstract class.
public abstract class AbstractWheeledVehicle extends AbstractVehicle
{

  private final int wheelCount;

  /** @return The number of wheels on the vehicle. */
  public int getWheelCount()
  {
    return wheelCount;
  }

  /**
   * Wheel count constructor.
   * @param wheelCount The number of wheels; must not be negative.
   */
  public AbstractWheeledVehicle(final int wheelCount)
  {
    //TODO use a precondition to ensure what wheelCount is not negative
    this.wheelCount = wheelCount;
  }

}
public class Car extends AbstractWheeledVehicle
{

  /** Wheel count constructor.
   * @param wheelCount The number of wheels; must not be negative.
   */
  public Car(final int wheelCount)
  {
    super(wheelCount);
  }

}

The reason you must call some constructor, either explicitly or implicitly, of the super class is that Java must construct and initialize each base class before constructing the subclass. Until the super class has been fully constructed, the subclass is in a limbo state and its variables are not available.

instanceof

To find out whether some reference is to an instance of a particular class, you use the Java instanceof operator. When wouldn't the instance type be clear? Consider the concept of polymorphism, in which you are provided with a reference of a base class without knowing which base class. Together with type casting (which you learned about in an early lesson about types), you can access functionality that may only be available through one of the subclasses, such as AbstractWheeledVehicle.getWheelCount() above.

Determining the runtime type of a Vehicle instance.
public static printVehicleDescription(final Vehicle vehicle)
{
  if(vehicle instanceof AbstractWheeledVehicle)
  {
    final AbstractWheeledVehicle wheeledVehicle = (AbstractWheeledVehicle)vehicle;
    System.out.println(String.format("Wheel count: %s", wheeledVehicle.getWheelCount());
  }

}

Review

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Your company's marketing manager comes to you and says that users now want to know, not just the top best-selling books of all times, but also the current top largest circulation magazines in the United States, as reported by Wikipedia on 2015-09-08.

  1. Perhaps you think for a moment and decide that, in the real world, books and magazines are both types of publications . Both books and magazines share some things in common, such as a “name” or “title”; the number in print, and date of first publication. Create a base class to represent a “publication”, and have both the “book” and “magazine” classes extend the “publication” class.
  2. Create appropriate unit tests for the new “magazine” class.
  3. You decide that there is no point for anyone to create a bare “publication” instance (that isn't a “book” or a “magazine”, in other words). Find some way to prevent the “publication” class from being instantiated by itself.
  4. Rather than keep separate arrays, you want to just mix the books and magazines together. Replace the array of books with an array of publications.
  5. You now have to fulfill the new requirements. Create two methods, one to print out books and another to print out magazines.
    • For each printing method, allow it to accept varargs of the “publication” type.
    • The book printing method will ignore non-books.
    • The magazine printing method will ignore non-magazines.
  6. To make your life easier as a developer for logging, override toString() for both the “book” and “magazine” classes to provide useful information to a developer.
  7. As a debugging aid, after printing out the books and magazines separately, bulk-print all the publications using their toString() methods.

See Also

References

Acknowledgments