Inheritance
Goals
- Understand and use inheritance.
- Know the difference between inheritance and composition.
- Understand and use abstract classes.
- Be able to override methods.
- Be able to call a super class constructor.
- Override and use the
Object.toString()
method.
Concepts
- abstract class
- base class
- compile time
- composition
- concrete class
- Diamond Problem
- encapsulation
- final class
- final method
- inheritance
- Liskov Substitution Principle
- multiple inheritance
- override
- polymorphism
- refactoring
- regression test
- run time
- single inheritance
- subclass
- super class
Language
abstract
extends
final
instanceof
@Override
Javadoc
{@Inheritdoc}
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:
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.
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.
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.
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.
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:
- the contract must be written sufficiently general to allow for variations in implementation, while concentrating on the central semantics of the behavior; and
- 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.

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()
.
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.
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.
/**
* 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.
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.
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
- Inheritance has several shortcomings:
- Like real-world categories, it is easy to separate classes based upon differing and/or overlapping criteria, such as number of wheels and utility for
Vehicle
types; this leads to inflexibility for future specialization. - Java only allows single inheritance.
- Like real-world categories, it is easy to separate classes based upon differing and/or overlapping criteria, such as number of wheels and utility for
- If you override a method and want to add functionality instead of replace functionality, don't forget to call the
super
version of the method.
In the Real World
- Inheritance is being less favored nowadays, because isn't as flexible as other techniques such as composition and interfaces for sharing commonalities.
- Avoid calling instance methods during the constructor if a child class might override those methods.
- Make instance variables
private
unless there is an exceptional need to do otherwise, and force all access to the values, even from subclasses, to go through accessor methods.
Think About It
- Don't blindly use inheritance because you think that inheritance hierarchy exists in the real world. Think of the most elegant way to establish relationships to bring about some functionality. Inheritance is only one tool; composition or interfaces may many times be a better approach.
Self Evaluation
- What are the three central features that make a programming language object-oriented?
- How do you make a subclass of
Object
in Java? - How could you prevent someone from extending your class?
- How could you prevent someone from extending your method?
- How would you write Javadocs for a method that
@Override
s another, if you only want to explain how the overriding method differs from the other? - What is an abstract class? What are they useful for?
- What is the
toString()
method? How do you get one? How is it used? Is the resulting value a good thing to place in a user interface? Why or why not? - What is multiple inheritance, and how does it relate to Java?
- What does the phrase “prefer composition over inheritance” mean? Can you think of an example to illustrate this idea?
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.
- 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.
- Create appropriate unit tests for the new “magazine” class.
- 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.
- 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.
- 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.
- 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. - As a debugging aid, after printing out the books and magazines separately, bulk-print all the publications using their
toString()
methods.
See Also
References
- Inheritance (Wikipedia)
- Polymorphism (Wikipedia)
- Liskov substitution principle (Wikipedia)
- Multiple inheritance: The diamond problem (Wikipedia)
Acknowledgments
- Tiller truck photo by Mfield, Matthew Field (Own work) [CC BY-SA 3.0 or GFDL], via Wikimedia Commons.