Overview of This Lesson

Inheritance occurs when one class inherits the data and behaviours of another, so when it's done correctly, it can cut down on redundant/repetitive code. Inheritance is a large topic, and this lesson introduces you to the concepts of Inheritance and how to start coding parent and child classes.

Pre-Requisites

It is assumed that you've gone over the Introduction to Class Relationships and Aggregation and Composition lessons and are already somewhat familiar with Dependency, Association, Aggregation, and Composition.

Introduction to Inheritance

Inheritance occurs when one class inherits attributes and methods from another class. Classes that inherit are called child classes (also called sub classes). These classes inherit from parent classes (also called super classes). It is often done when you have a set of common methods or attributes in a set of two or more classes: You can make the parent class more general, and it can contain the common features. Child classes will be more specific and will add to or modify the features inherited from the parent class.

This is often referred to as an "is a" relationship, because most of the time you can say something like [child class name] is a [parent class name]. For example, "Circle is a Shape" or "SalariedEmployee is an Employee". When we first started talking about class relationships, we said "A Rose is a Flower". The Flower class is the parent of the child class Rose.

Another example that is commonly used in OOP textbooks is a class hierarchy of animals. Say you have a Dog class. A Dog class could have attributes for colour, name and working (true/false - some dogs are working dogs), and methods such as eat(), bark(), and rollOver(). You might also have a Cat class. A Cat class could have attributes for colour, name, and declawed (true/false), and methods such as eat(), meow(), and scratchFurniture(). Both the Cat class and the Dog class have colour and name attributes, and they both have an eat() method. Instead of writing these attributes (and their accessor/mutator methods) and the eat() method each time in both classes, it's better to put these common elements into one class. Therefore, we might design a class called Pet that contained the attributes name and colour, and the method eat(). We would make Pet the parent class, and the Dog and Cat class would be child classes. The Dog and Cat classes inherit the name and colour attributes and the eat() method. We can then add working, bark() and rollOver() to Dog, and declawed, meow() and scratchFurniture() to Cat.

a hierarchy chart of animals
An inheritance hierarchy showing that Pet is the Parent class. Pet has 2 child classes: Dog and Cat.

Sometimes inheritance is also called Generalization.

Inheritance in UML

Inheritance is shown with a solid line that connects the parent class to the child class(es). There is an arrow at the end of the line that points to the parent. Example:

UML showing Inheritance
Example of Inheritance in UML: Flasher and Streetlight are child classes (or sub classes) of the Light class.

Coding Inheritance

Let's code a couple of classes with an inheritance relationship. First, examine the UML for these two classes: Circle models a circle object with a specific radius, and Cylinder models a cylinder object with a specific radius and height. Both Circle and Cylinder can calculate their area (for Cylinder, that would be the surface area). Only Cylinder can calculate its volume. Circle can calculate its circumference.. but wouldn't it make sense that someone might want to know the circumference of a cylinder, also? Both classes have a a toString() and an equals() method.

Class: Circle
Data Members:
- radius : double
Methods:
+ Circle()
+ Circle(radius : double)
+ setRadius(radius : double) : void
+ getRadius() : double
+ calcArea() : double
+ calcCircumference() : double
+ toString() : String
+ equals(circle : Object) : boolean
Class: Cylinder
Data Members:
- radius : double
- height : double
Methods:
+ Cylinder()
+ Cylinder(radius : double, height : double)
+ setRadius(radius : double) : void
+ getRadius() : double
+ setHeight(height : double) : void
+ getHeight() : double
+ calcArea() : double
+ calcVolume() : double
+ toString() : String
+ equals(circle : Object) : boolean

You will see some redundancies between these two classes. Not only does this mean the programmer is writing some code twice, but any modifications need to be made to the common code in the future will result in a lot of unnecessary maintenance. This is not desirable and goes against the point of object-oriented programming.

Inheritance means that one class can inherit or use code from another class as long as there is a child-parent relationship between the classes. If we make Circle the parent class, we can then program Cylinder as the child class. This will allow Cylinder to make use of code in the Circle class.

To see how this works, write the code for the Circle class:

public class Circle {
          
    private double radius = 1;
        
    public Circle() {	
    }
        
    public Circle(double radius) throws IllegalArgumentException {
        setRadius(radius);
    }
        
    public void setRadius(double radius) {
        if (radius > 0)
            this.radius = radius;
        else
            throw new IllegalArgumentException("Invalid value for radius.");
    }
        
    public double getRadius() {
        return radius;
    }
        
    public double calcArea() {
        return Math.PI * Math.pow(radius, 2);
    }
          
    public double calcCircumference() {
        return 2 * Math.PI * radius;
    }
        
    public String toString() {
        return "Circle: radius=" + radius;
    }
        
    public boolean equals(Object circle) {
        Circle c = (Circle)circle;
        return (c.getRadius() == this.radius);
    }
}

Test your class to make sure everything works. Then we can start writing the Cylinder class.

When designing Cylinder, we realize that the radius attribute and its accessor and mutator methods are already defined in Circle. We also see that we have three methods that are the same (getArea(), toString(), and equals()) but will have different code. We will focus on these items as we go through this example.

To indicate that a class is inheriting from another class, you use the extends keyword in the class signature:

public class Cylinder extends Circle {
          
}

This indicates that Cylinder is a child class of Circle. You don't have to define the radius variable or its get and set methods in Cylinder" Cylinder inherits these members from the Circle class. We do need to define the height variable and it's accessor and mutator methods, because these members are not part of the Circle class, only the Cylinder class:

public class Cylinder extends Circle {
    private double height = 1;
        
    public void setHeight(double height) {
        if (height > 0)
            this.height = height;
        else
            throw new IllegalArgumentException("Invalid value for height.");
    }
        
    public double getHeight() {
        return height;
    }
}

We also need the two constructors for this method. Constructors are NOT inherited from the parent class, so if you want more than a default constructor (recall that the compiler will add a default constructor to a class if you don't add any constructors), you will have to add them to your child classes. Our default constructor should set radius and height to the default values, and two-parameter constructor will need to validate radius and height by using their set methods.

In the default constructor, we could just assign radius a value of 1:

public class Cylinder extends Circle {
    private double height = 1;
        
    public Cylinder() {
        radius = 1;
    }
        
    public void setHeight(double height) {
        if (height > 0)
            this.height = height;
        else
            throw new IllegalArgumentException("Invalid value for height.");
        }
        
    public double getHeight() {
        return height;
    }
}

However, radius is private inside the Circle class.. we can't access it! We could use the setRadius() method, since that's a public method inside the Circle class, it's inherited by Cylinder. Instead, it would be easier to just tell the parent class's default constructor to execute. You can call a parent constructor from a child constructor by using the super keyword:

public class Cylinder extends Circle {
    private double height = 1;
        
    public Cylinder() {
        super();
    }
        
    public void setHeight(double height) {
        if (height > 0)
            this.height = height;
        else
            throw new IllegalArgumentException("Invalid value for height.");
    }
        
    public double getHeight() {
        return height;
    }
}

In this case, we are calling the parent class default constructor, which sets the radius to a default value of 1.

In the second constructor, we want to set radius and height to the programmer-specified values. Here we can use the set method for radius (defined in the parent class) and the set method for height we just added to our Cylinder class. However, what if we had other code in the Circle's constructors that needed to execute when we instantiated a Cylinder? We are already calling the parent constructor in the Cylinder's default constructor, but what about Cylinder's 2-param constructor? This often happens, so when you have any kind of constructor in a child class, you should always call the parent constructor. In the case of the Cylinder 2-param constructor, you can call the Circle's 1-param constructor and give it the radius specified in the Cylinder constructor:

public class Cylinder extends Circle {
    private double height = 1;
        
    public Cylinder() {
        super();
    }
        
    public Cylinder(double radius, double height) {
        super(radius);
        setHeight(height);
    }
        
    public void setHeight(double height) {
        if (height > 0)
            this.height = height;
        else
            throw new IllegalArgumentException("Invalid value for height.");
    }
        
    public double getHeight() {
        return height;
    }
}

This code will invoke the single-parameter Circle constructor, passing the value of radius into it. Obviously this will run the setRadius() method in the Circle class, which validates and sets the radius variable. This is a common way for programmers to set values that are inherited from the parent class.

Test this class in your test program from earlier. You'll notice that everything works, even though we didn't define anything in Cylinder for radius. This is because of the inheritance that allows Cylinder to implement code from Circle.

Method Overriding

When you create a child class, you may want to inherit methods from the parent class as we did with the radius accessor and mutator. You can also add methods and instance variables as we did with height and its accessor and mutator methods. You can also modify methods that are inherited from the parent class. This is referred to as method overriding -- think of it as overriding or creating a different version of an inherited method.

When you override a method, the method signature must remain the same as the method in the parent class. The body of the method can be different (and generally, that's the point). In Cylinder, the calcArea() method will perform differently: it will calculate and return the cylinder's surface area according to the following formula:

2 * PI * radius2 + height * 2 * PI * radius

You should have noticed that the PI * radius2 part of this formula is actually the circle's area! This will become useful when we code our method, as we can tell Cylinder's calcArea() to go and get the result from Circle's calcArea()! To do this, you use the super keyword to refer to the parent class's calcArea() method::

public double calcArea() {
    return 2 * super.calcArea() + height * 2 * Math.PI * radius;
}

super refers to the Circle class definition, so this piece of the statement is invoking the calcArea() method from Circle. You must include super here, otherwise the calcArea() method will try to call itself (this is called recursion).

What happens when you compile the Cylinder class now? You'll get the following error:

Cylinder.java:33: radius has private access in Circle
                    return 2 * super.calcArea() * height * 2 * Math.PI * radius;
                                                                   ^
    1 error

The radius variable is private in the Circle class -- you must use the getRadius() method to access it!

public double calcArea() {
    return 2 * super.calcArea() * height * 2 * Math.PI * getRadius();
}

Exercises

1. Override toString() method, and add the calcVolume() method in the Cylinder class. A cylinder's volume is its height * the circle's area.

2. Add an equals() method to your classes. What makes two circles equal? What would make two cylinder's equal?

Constructor Chaining

You might notice in your IDE when you have a constructor that calls set-methods directly, you get a strange warning, "overridable method call in constructor". What is that all about? What does it mean and how do we fix it?

the Cylinder 
                     constructor calling super(radius) and then calling 
                     setHeight(height); there's a warning on the setHeight()
                     line about overridable method call in constructor
The warning "overridable method call in constructor" appears when you call an instance method inside a child class constructor.

What is Constructor Chaining?

Constructor chaining occurs when you construct an instance of a child class: before invoking the child class constructor, first the parent class constructor is executed. If that parent class has a parent class, then the parent's parent constructor executes before the parent constructor. This continues right up the inheritance chain

For example, imagine ClassC, which is a child of ClassB, and ClassB is a child of ClassA. When you instantiate ClassC:

ClassC classc = new ClassC();

This calls the ClassC() constructor. Before any code inside the constructor executes, the ClassB() constructor is invoked. Before any of the code inside ClassB() executes, the ClassA() constructor is invoked. ClassA is at the top of the inheritance chain, so the ClassA() constructor's code executes.

an inheritance chain with 3 classes
The constructor chaining process

When constructor ClassA() is finished executing, control moves back to the ClassB() constructor, which finishes executing. Once the ClassB() constructor is finished, then control moves back to ClassC(), and the ClassC() constructor code executes.

The best way to truely understand constructor chaining is with a practical demonstration:

Start a new project and add these 3 classes:

(The classes below are just examples and the docs are purposely omitted. Also note that we NEVER print to the console inside a class - we only do this here for demonstration purposes!)

public class Animal {
    
    private String name = "unknown";
    
    public Animal() {
        System.out.println("Animal Default Constructor");
    }
    
    public Animal(String name) {
        setName(name);
        System.out.println(doStuff());
    }
    
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty())
            this.name = name;
        else
            throw new IllegalArgumentException("Name can't be empty.");
    }
    
    public String getName() {
        return name;
    }
    
    public String doStuff() {
        return "Animal: " + name;
    }
}
public class Pet extends Animal {

    public String breed = "";

    public Pet() {
        System.out.println("Pet Default Constructor");
    }

    public Pet(String name, String breed) {
        super(name);
        setBreed(breed);
        System.out.println(doStuff());
    }

    public void setBreed(String breed) {
        this.breed = breed;
    }

    public String getBreed() {
        return breed;
    }
    
    public String doStuff() {
        return "Pet " + getName() + " is a " + breed;
    }
}
public class Cat extends Pet {

    public Cat() {
        System.out.println("Cat Default Constructor");
    }

    public Cat(String name, String breed) {
        super(name, breed);
    }
}

In the Main Class for your project, add the following statement inside your main():

Cat cat = new Cat();

Now run your Main class. Note the output you get:

Animal Default Constructor
Pet Default Constructor
Cat Default Constructor

You can clearly see that the Animal default constructor executed, then Pet default constructor, then the Cat default constructor.

Recall that unless you say otherwise, a call to super() (parent default constructor) is automatic in every child constructor.

Program flow is as follows (follow along in the code as you read, you can also watch the steps in action by using the Debug tools in NetBeans):

  1. Our statement in main() is invoking the Cat() default constructor.
  2. Before the code in Cat() executes, it first invokes super(), which calls the Pet() constructor.
  3. Before the code inside Pet() executes, it first invokes super(), which calls the Animal() constructor.
  4. The Animal class is the parent of our hierarchy, so the print statement inside Animal() executes. Technically, Animal is a child of the Object class, so the Animal() constructor does call super() which executes the Object() constructor, but there's nothing in Object() that we need to worry about.
  5. Then Animal() is done, so program flow returns back to Pet().
  6. The print statement inside Pet() executes, then program flow returns to Cat(), where the print statement in Cat() executes.
  7. Then the program returns to the main() method.

This is an example of constructor chaining!

Dynamic Binding

Dynamic Binding is a term that describes how Java determines which method to execute when overridden or inherited methods are called by looking to see what the actual type of the calling object is. When you have an inheritance chain with overridden or inherited methods, there might be multiple choices when a method call is made. For example, if you have an instance of Animal:

Animal foo = new Animal();

...and then you want to invoke the doStuff() method, it should be clear that calling foo.doStuff() will invoke the Animal class's doStuff() method and not the Pet class's doStuff() method: that's because even though Animal is a child of Pet, Java will invoke Animal's doStuff() because the actual type of the foo object is Animal

However, you're also permitted to write a statement such as:

Animal bar = new Pet();

This constructs a Pet instance and references it with the Animal variable bar. The actual type of bar is Pet, but the declared type of bar is Animal.

If you were to then try to execute bar.doStuff();, would Pet's doStuff() execute or would Animal's doStuff() execute? Dynamic binding states that we must look at what the actual type of the object is: in this case the actual type is Pet, therefore the Pet's doStuff() method will execute.

Overridable Method Call in Constructor Warning

In the Pet class, on line 13 (setBreed(breed); inside the single-param constructor) you get the warning in the left margin "overrideable method call in constructor". To understand what this means:

Comment out the previous exercise code in your project's main() and add this statement:

Pet pet = new Pet("Arti", "Tabby");

Run the Main project. Note the output after you run your Main class:

Pet Arti is a null
Pet Arti is a Tabby

Why do you get this output? The program flow is as follows (follow along in the code as you read, you can also watch the steps in action by using the Debug tools in NetBeans):

  1. Pet(name, breed) constructor is invoked from inside main()
  2. Pet(name, breed) constructor calls super(name), which is the Animal(name) constructor.
  3. Animal(name) constructor first calls setName(name), which sets the name data member to "Arti".
  4. Animal(name) invokes the doStuff() method.

    This is where things get interesting!!

    Due to dynamic binding, the doStuff() method in the Pet class is invoked, not the one in Animal, because the actual type of the object is Pet!

    When Java sees that there is a call to doStuff(), it remembers that we're actually in the process of creating a Pet, not an Animal, so it knows that it needs to execute the version in Pet.

  5. The Pet.doStuff() method returns the String "Pet Arti is a null" back to the Animal(name) constructor. This is how that happens:
    1. Pet.doStuff() concatenates "Pet " and getName() (which invokes the getName() from Animal, and that returns the String "Arti"). setName() was called earlier in Step 3, so we know that name contains "Arti".
    2. Then Pet.doStuff() concatenates the literal " is a ", and then it concatenates the breed member value.
    3. However, breed has not been set, yet - that won't happen until Step 8 below. Therefore, breed is currently a null object. This is why the string "null" gets concatenated onto the end of the string, giving us "Pet Arti is a null".
  6. Next, Animal(name) accepts the "Pet Arti is a null" string and sends it to println(), which results in the first line of output.
  7. Animal(name) is finished, and now control returns back to Pet(name, breed)
  8. Pet(name, breed) calls setBreed(breed), which sets the breed member to "Tabby"
  9. Then the doStuff() method is invoked. We're inside the Pet class now, so again the Pet.doStuff() method is executed.
  10. Pet.doStuff() now has a value in the breed member (step 8), so the String "Pet Arti is a Tabby" is built and returned.
  11. Pet(name, breed) accepts the "Pet Arti is a Tabby" string and sends it to println(), which results in the second line of output.
  12. The Pet constructor is finished; control returns to main(), which is also finished.

This demonstrates why it's not recommended that you call methods in your constructor that someone can override: it can cause bugs if those methods are accessing child members that may not have been initialized, yet.

So, Solutions?

The second option is the better solution and what most programmers do. I use a private init() method, like this:

public Circle(double radius) {
    init(radius);
}

private void init(double radius) {
    setRadius(radius);
}
public Cylinder(double radius, double height) {
    super(radius);
    init(height);
}

private void init(double height) {
    setHeight(height);
}

Methods defined as private or final can't be overriden in a child class. Should you choose private methods or public final methods? That depends:

Use private methods when you don't need your init() method to be seen/used by programmers using your class. No one needs to use my init() methods outside of Circle and Cylinder: if they want to set radius and height to initialize a shape, they can construct a default shape and then use setRadius() and setHeight().

Use public final methods when a programmer will need to use those methods when they use your class. You would javadoc these methods normally. Here's a simple example using public final init method:

public class CatCafe {

    public static final String CAFE_NAME = "Wendi's Cat Cafe";
    public static final int CHAIRS_PER_TABLE = 4;

    private int numTables;
    private int numChairs;
    
    public CatCafe() {  
        // sets # of tables and chairs to default
        init(10);
    }

    public CatCafe(int numTables) {
        // set # of tables and chairs
        init(numTables);
    }

    public final init(int numTables) throws IllegalArgumentException {
        // make sure number of tables is valid
        setNumTables(numTables);
        // set #chairs if #tables is ok
        numChairs = numTables * CHAIRS_PER_TABLE;
    }

    public void setNumTables(int num) {
        if (num > 0)
            numTables = num;
        else
            throw new IllegalArgumentException("Number of tables must be > 0.");
    }

    public int getNumTables() {
        return numTables;
    }
    
    public int getNumChairs() {
        return numChairs;
    }
}

Exercises

1. Write a class called Sphere with the following specifications:

Class: Sphere extends Circle
Data Members:
-radius:double
Methods:
+ Sphere()
+ Sphere(radius : double)
+ getRadius() : double
+ setRadius(radius : double) : void
+ calcArea() : double
+ calcVolume() : double
+ toString() : String
+ equals(sphere : Object) : boolean

The surface area of a sphere is 4 * PI * radius2 (4 times circle area) and the volume of a sphere is 4/3 * PI * r3, which could also be written as 4/3 * radius * the circle area.

2. A kiosk for Martin's Sweet Farm at a farmer's market sells products that they make from their honey bee farm. They sell honey, of course! But they also sell products made from honey and bee's wax such as soap, lip balm, and candles. Many of their products have flavours (which might actually be scents or colours, but they always use the word "flavour"), for example they have Chili Honey (a delicious honey with a chili pepper in the jar that infuses the honey with a nip of pepper taste!), Strawberry-Mint Lip Balm, and Tea Tree Mint Honey Soap. They keep track of inventory with a program that uses the following classes:

Class: Product
Data Members:
- productName : String
- flavour : String
- cost : double
Methods:
+ Product()
+ Product(name : String, flavour : String, cost : double)
+ getProductName() : String
+ setProductName(name : String) : void
+ getFlavour() : String
+ setFlavour(flavour : String) : void
+ getCost() : double
+ setCost(cost : double) : void
+ toString() : String
Class: Honey extends Product
Data Members:
- type : String
- jarSize : double
Methods:
+ Honey()
+ Honey(name : String, flavour : String, cost : double, type : String, size : double)
+ getType() : String
+ setType(type : String) : void
+ getJarSize() : double
+ setJarSize(size : double) : void
+ toString() : String

Honey is a Product, so Honey is an extension of Product.

Honey has 2 extra data members: type is the type of honey (e.g. Wildflower, Buckwheat), and jarSize is the size of the jar (they sell currently sell 250g, 500g, and 700g jars, so a typical value would be 500.0).

All the string values in both classes are mandatory, they can't be empty or null objects. Product name defaults to "Miscellaneous", flavour defaults to "plain", and type defaults to "Wildflower".

The cost and the jar size must both be greater than 0. Cost defaults to 2.5 and jar size defaults to 500.0.

The toString() for both classes returns the product name and the cost in the following format:

Product Name: $x.xx

(cost formatted with a $ and to two decimal places)

Write the Product and Honey classes.