Unit 10, part 1: Inheritance
Topics covered in this unit
By the time you are done with this unit you will:
- know how to create a subclass of a superclass
- understand the "is-a" relationship between a superclass and a subclass, and how these are represented in the BlueJ IDE
- explain how
BankAccount - SavingsAccount - CheckingAccount
demonstrates inheritance - use the
super()
syntax to pass arguments to a superclass constructor - be able to explain, discuss, and write other examples of super- and subclasses as needed (
Person - Teacher - Student
, etc.) - be able to write methods that override those of superclasses, including the
toString()
method - be able to define polymorphism and to give examples of how it is used
- explain what an abstract class is and how it can be used in a project
10.0. Overview - Inheritance and Inheritance Hierarchies
At this point we understand basic data structures, basic control structures, and basic Object-Oriented design principles. We have two more Object-Oriented concepts to discuss before moving on to a more pure discussion of some Computer Science topics.
The first of these two Object-Oriented concepts is that of inheritance, the ability to write our classes in such a way that they can be hierarchically organized. This has an enormous number of benefits for us, both in reducing the amount of code that we have to write, and improving the ways that we use the code that we do write.
Inheritance will make immediate logical sense to you once you see it... so let's get started!
- Hierarchies: Superclasses and subclasses
- Inheriting Instance Fields and Methods
- The Question Class
- Abstract Classes
- Object: The Cosmic Superclass
10.1. Hierarchies: Superclasses and subclasses
There are lots of ways that we try to re-use code when programming, including using loops, and writing classes and methods that can be used repeatedly.
Another important means of re-using code is called inheritance: using a previously-defined class as a starting point for writing a class that is mostly similar in structure and behavior.
Inheritance
Inheritance is a mechanism for extending existing classes to create new, more specialized classes ("subclasses"). These subclasses use the same fields (variables) and methods of the class they inherit from, and also have their own additional fields and methods.
10.1.1. Using Inheritance with BankAccount
Up to this point, for example, we've been using BankAccount
for creating checking accounts, savings accounts, and whatever else we want. To do this, our strategy has been to take the original BankAccount
class and modify it to meet our needs.
There's a better way to do this, though. We can use the original BankAccount
class as is, and create other classes that build on the original without having to rewrite it.
Here's a diagram of a BankAccount inheritance hierarchy.

We say that the CheckingAccount
and SavingsAccount
classes are subclasses of CheckingAccount
, and that they "extend" CheckingAccount
.
SavingsAccount
, by extending BankAccount
, automatically inherits all methods and fields from BankAccount
.
Also, the BankAccount
class is referred to as the superclass of its subclasses.
The superclass is the more generalized form of a class, while subclasses are more specialized forms that extend the features of the superclass.
What does the Java source code for this look like?
The BankAccount
class looks just like it did before:
Show/hide answer BankAccount
class
/** A bank account has a balance that can be changed by deposits and withdrawals. */ public class BankAccount { private double balance; // instance field declaration /** Constructs a bank account with a zero balance. */ public BankAccount() { balance = 0; } /** Constructs a bank account with an given balance. @param initialBalance the initial balance of the account */ public BankAccount(double initialBalance) { balance = initialBalance; } /** Deposits money into the bank account @param amount the amount to deposit */ public void deposit(double amount) { balance = balance + amount; } /** Withdraws money from the bank account @param amount = the amount to be withdrawn */ public void withdraw(double amount) { balance = balance - amount; } /** Gets the current balance of the bank account */ public double getBalance() { return balance; } }
10.1.2. Implementing subclasses
So what does the SavingsAccount
class look like, then? How do we indicate that it's going to inherit from BankAccount
?
It starts with this header:
public class SavingsAccount extends BankAccount { // We'll be able to use the BankAccount fields (balance
, // via methods) // We'll be able to use BankAccount methods.getBalance()
, //.deposit()
, and.withdraw()
// We'll also be able to define new instance fields specialized for // SavingsAccount objects // We'll also be able to define new methods specialized // for SavingsAccount objects }
The reason we like inheritance is that it allows us to re-use code. If we know that our SavingsAccount
is going to be needing .deposit()
, .withdraw()
, and .getBalance()
methods, we don't have to go to the trouble of writing and testing those methods again—they've been written and tested in the BankAccount
class already. We can just extend
that class to take advantage of those methods, and at the same time add a few new ones based on the needs of the subclass.
Two reasons we like Inheritance
There are actually two really good reasons why you should appreciate inheritance.
The first we've already mentioned: inheritance allows for the logical re-use of code / classes that has already been written. Whether you're extending a class that you yourself have written, or extending one that is already provided in the Java API, writing a subclass that extends a pre-existing superclass can save you a lot of time and effort.
The second reason why inheritance is amazing is that you can use algorithms with a superclass and all its subclasses.
For example, if my Bank
class is going to use an ArrayList
to manage a bunch of BankAccount
objects, what kind of objects should I store in the array? I've got regular bank accounts, I've got savings accounts, I've got checking accounts... How can I use a single ArrayList
to manage all these different kinds of accounts?
In Java, you can't, unless you have an ArrayList<BankAccount>
. If you set that up, then the Bank
will be able to manage BankAccounts
and any subclasses that inherit from BankAccount
!
Without inheritance, we wouldn't be able to pull this off.
10.1.3. Features of the SavingsAccount
class
A savings account is different from a checking account in that you earn some sort of interest on the money deposited.
What do we need in the new SavingsAccount
class that's different from what we already have in the regular BankAccount
?
- We need to know what the
interestRate
is, - We need a method called
.addInterest()
that we can use to add interest at the appropriate time, and - We need a constructor that will create a
SavingsAccount
with a given interest rate.
Take a look at this complete SavingsAccount
class here and see what we've done.
public class SavingsAccount extends BankAccount { double interestRate; /** * Constructor for objects of class SavingsAccount */ public SavingsAccount(double rate) { interestRate = rate; } /** * Overloaded constructor for objects of class SavingsAccount */ public SavingsAccount(double initialBalance, double rate) { super(initialBalance); // previous line delivers the initialBalance to the super class interestRate = rate; } /** * addInterest adds the required interest to the account */ public void addInterest() { double interest = getBalance() * interestRate / 100; deposit(interest); // uses the deposit method of the superclass } }
Note that we're not referring to the balance
of the BankAccount
class directly—it has been declared as private
, so we can't use it directly (even though we've inherited it!). We have to access the balance via the public getBalance
method.
Recall that the BankAccount
class had an overloaded constructor that allowed us to create a new account with an initial balance as a parameter. Notice that we've done the same thing with the SavingsAccount
class here. We've got an issue, however: how can we send the initial balance of our savings account up to the BankAccount constructor?
Sending construction parameters to the superclass
The instance variables in a superclass by a subclass via public methods in the superclass.
To initialize an instance variable in the superclass during construction, use the super()
statement as the first line in the subclass's constructor, with the construction parameters needed for that superclass enclosed in parentheses.
See the SavingsAccount
constructor above for an example.
10.2. Inheriting Instance Fields and Methods
Let's look at some of the more technical details of inheritance.
Subclass and superclass methods can interact
When it comes to methods, there are three ways that a subclass can extend a superclass:
- You can inherit methods from the superclass. If you don't override the superclass, you automatically inherit the methods of the superclass.
- You can define new methods. Methods that don't exist in the superclass can only be applied to subclass objects. (
SavingsAccount.addInterest()
for example, will only work with aSavingsAccount
, and not with the superclassBankAccount
). - You can override methods that have been previously defined in the super class. To do this, you specify a method with the same signature (name and parameter type(s) in the header), and when the method is applied to the subclass (
CheckingAccount.deposit()
rather thanBankAccount.deposit()
), the local, overriding method is applied.
Instance fields (variables) work a little differently, though.
Subclass and superclass variables can interact
There are three ways that instance variables (fields) work in subclasses and superclasses:
- A subclass can never override instance fields.
- The subclass inherits all fields from the superclass, automatically. So all subclasses of the
BankAccount
class inherit the instance fieldbalance
. Note that they inherit it but they can't access it directly if it's private! The subclass has to work through the public methods of the superclass. - Any new instance fields that you define in the subclass are present only in that subclass. So a
BankAccount
, as a superclass, doesn't have access to theinterestRate
of aSavingsAccount
.
10.3. The Question
class
Let's look at an example from Horstmann's Java Concepts: Early objects, 8th Ed.
A quiz or test question is a type of thing that could easily be described by a class in Java. All quiz/test questions consist of a question
and a correct answer
. So what would that class look like?
The Question
class
Take a moment to consider what instance variables and methods you would need to be able to pose and identify the answer for a quiz question.
What would the complete code for that class look like?
/** * A Question class */ public class Question { // instance variables private String question; private String answer; /** * Constructor for objects of class Question */ public Question() { question = ""; answer = ""; } /** * Overloaded constructor for objects of class Question */ public Question(String question, String answer) { this.question = question; this.answer = answer; } /** * Sets the question text. * @param questionText the text of this question */ public void setQuestion(String theQuestion) { question = theQuestion; } /** * Sets the answer for this question * @param correctResponse the correct answer */ public void setAnswer(String correctResponse) { answer = correctResponse; } /** * Checks a given response for correctness * @param response the response to check * @return true if the response was correct, false otherwise */ public boolean isCorrect(String response) { return answer.equals(response); } /** * Displays the question */ public void display() { System.out.println(question); } }
This Question
class is pretty straightforward. Let's use it as a jumping off point for creating some subclasses that inherit from it.
10.3.1. Implementing a subclass
Our simple Question
class works just fine for simple questions, but how would we use it to define a multiple-choice question?
We can't, at least not easily. And we probably don't want to have to write a completely new Question
class.
We can actually use the concept of inheritance to consider writing lots of different question classes, all of which "inherit from the Question
class", or which "extend the Question
class."
Here's a UML diagram showing some of the different types of questions we might want to consider:

10.1.2. The ChoiceQuestion
subclass
Because a subclass inherits instance variables and methods from its superclass, we don't focus on those commonalities when considering a subclass. To write a subclass, you identify the qualities that make the subclass different from its superclass.
What is it that makes a ChoiceQuestion different from a plain Question?
- The object will have to store a number of possible responses for the question.
- The object will need a method for adding those multiple answer choices.
- The
display
method will need to be altered so that it can display the multiple choices available to the user.
Our code for the ChoiceQuestion
class will need to address these three differences. Let's see what the headers for that code look like:
/** * Describes a multiple choice question in terms of the multiple choices * that can be selected from. */ public class ChoiceQuestion extends Question { // instance variables private ArrayList<String> choices; /** * Adds a possible answer to the list of multiple-choice responses * @param choice the text of the response * @param correct True if the answer is the correct one, otherwise False */ public void addChoice(String choice, boolean correct) { // to be completed later } public void display() { // to be completed later } }
Let's continue looking at the ChoiceQuestion
subclass and see how methods and instance fields for the questions interact with each other.
10.3.2. Writing the addChoice
method
Recall that the ChoiceQuestion
class needs to do three things:
- The object will have to store multiple responses for the question.
- The object will need a method for adding multiple answer choices.
- The
display
method will need to be altered so that it can display the multiple choices available to the user.
Take a look at the code structure for ChoiceQuestion
given above. How are we going to implement the addChoice
method?
The headers above give us a clue. This multiple-choice question is going to store the multiple answers in a String ArrayList, and the addChoice
method is going to add items to that array, along with an indication of whether the given answer is true
(the correct answer) or false
.
In creating the question, then, we would add multiple responses like this:
choiceQuestion.addChoice("It's a syntax error.", true);
So, what does the code look like for implementing this method?
public void addChoice(String answer, boolean correct) { choices.add(answer); // add it to the list of options if (correct) { // we need to set the correct answer in the superclass! // Convert choices.size() to a string for storage as an answer String correctNumber = "" + choices.size(); // Why the "" ?! setAnswer(correctNumber); // can't access private variable directly! } }
10.3.4. Overriding the display
method
How do we go about displaying the question? For our subclass, we actually have to display two things now: the text of the question, and the options that the user will choose from.
Displaying the options is easy, because those are stored in our local variable, the choices
ArrayList:
public void display() { // display question // (to be completed later // display choices for (int i = 0; i < choices.size(); i++) { int choiceNumber = i + 1; // adjust for normal numbering System.out.println(choiceNumber + ": " + choices.get(i)); } }
But what about the question itself? That's stored away up in the superclass, in a private variable that we can't access from the subclass.
Can we call the superclass's display
method? We can't call it directly from the subclass, because when we do, we just call the local display
method that we're writing right now.
Our solution is to specify that we want to use the superclasses display
method:
super.display();
This will force the subclass to refer to the display method of the superclass.
10.3.5. Putting it all together
Based on the material that we've developed to this point:
- Write the complete
ChoiceQuestion
class. - Write a
QuestionDemo2
class that:- creates a two question multiple-choice quiz, and
- administers it to the user.
10.3.6. Polymorphism
In this section we'll discuss some of the more subtle details of super- and subclasses. In particular we'll be looking at how Java uses polymorphism in running code.
Definition: Polymorphism
Polymorphism ("multi-formed") in Java refers to the ability to perform similar tasks on different objects, with corresponding different strategies for executing those tasks.
Example: The display
method for our Question
class and our ChoiceQuestion
class does different things, depending on the type of object it's acting on.
10.3.7. Presenting Questions
Now that I have two types of questions that I can ask, let's write a new method, presentQuestion
, that I can use to display questions and get answers.
public static void presentQuestion(Question q) { q.display(); System.out.print("Your answer: "); Scanner in = new Scanner(System.in); String response = in.nextLine(); System.out.println(q.isCorrect(response)); }
This code works with Question
s. Does it also work with ChoiceQuestion
s? Could we do this?
ChoiceQuestion q2 = new ChoiceQuestion(); . . . presentQuestion(q2);
Converting Subclasses to Superclasses
A subclass reference can be used when a superclass reference is expected.
We can use a ChoiceQuestion
reference when a Question
reference is expected. The variable q2
is of the type ChoiceQuestion
, and the variable q
is of the type Question
, but they can both refer to the actual ChoiceQuestion
that we've created.
Note that Question q
can access the information in ChoiceQuestion
, but only information that is referred to in the superclass. Question q
doesn't know anything about the ArrayList choices
, for example. That information is hidden away in the subclass.
What about that line q.display()
in presentQuestion
? How does that work? When we call that, does it refer to the display method
described by Question
or that of ChoiceQuestion
?
In Java, method calls are determined by the type of the actual object, and not the reference. So if q
is referring to an actual Question
, the Question
display method will be used. But if q
refers to a ChoiceQuestion
, then ChoiceQuestion
's display method will be used.
We could say that the .display()
method is polymorphic: it acts differently, depending on the object that it's being called on.
Here's the advantage: Now that we've written presentQuestion(Question q)
, we never have to write it again. Even if we come up with additional types of questions, as long as they inherit from the Question
class, our presentQuestion
method will work just fine.
10.4. Abstract Classes
Recall that, when extending a class, you have three strategies for writing methods:
- overriding inherited methods by writing new versions of those methods for the subclass,
- inheriting a superclass method and using it as is, and
- writing your own new, specialized, methods for the subclass.
There may be occasions where you, writing what you anticipate will be a superclass, wish to force subclasses to override a method that you've written.
You do this by defining an abstract method.
Here's what you don't do:
public class BankAccount { public void deductFees() { // leaving this blank to be defined by subclasses } ... }
The deductFees()
method is just a blank method, and subclasses could choose to ignore it. If we want to force them to define a deductFees
method for their subclass, we create an abstract
method:
public class BankAccount { // Usual BankAccount stuff . public abstract void deductFees(); // <-- notice semicolon . . }
This method has no implementation specified—it's just a header—so it's called an abtract method; the subclasses have to provide a concrete implementation.
It turns out that you're not allowed to construct objects of classes with abstract methods, but we can still define the class—it just has to be an abstract class, which can't be instantiated. But we can extend it to create concrete subclasses.
So, continuing with this example, if we want to make deductFees
an abstract class, we need to make the BankAccount class an abstract class, like this:
public abstract class BankAccount { private int balance; // instance variable public BankAccount { balance = 0; } public void deposit(double amount) { balance = balance + amount; } . . . public abstract void deductFees(); // <-- notice semicolon . . }
Let's write an abstract class with some abstract methods so we can see how this all works.
Fifteen-Minute Challenge
Write an abstract class Shape
that has a concrete method getSides
and an abstract method getArea
. Then write two classes, Rectangle
and Circle
, that extend Shape
. Write a main program ShapeTester
that demonstrates the use of these two shapes.
Show/hide hint on writing abstract Shape
class
/** * Abstract class Shape - This superclass can be used by polygons * that inherit from it. * * @author Richard White * @version 2015-03-10 */ public abstract class Shape { // instance variables private int sides; /** * Constructs a shape * @param sides the number of sides in the shape */ public Shape(int sides) { this.sides = sides; } /** * Returns the number of sides in the Shape * @return number of sides */ public int getSides() // concrete method { return sides; } public abstract double getArea(); // abstract method }
Show/hide hint for writing the Rectangle
class
/** * The Rectangle class uses the abstract Shape superclass. * * @author Richard White * @version 2015-03-10 */ public class Rectangle extends Shape { // instance variables private double length; private double width; /** * Constructor for objects of class Rectangle */ public Rectangle(double theLength, double theWidth) { super(4); // sends the number of sides to the Shape superclass length = theLength; width = theWidth; } /** * getArea() implements the getArea abstract method described in the * abstract method of the superclass. * @return the area of the rectangle */ public double getArea() { return length * width; } }
10.5. Object: The Cosmic Superclass
In Java, every class is the direct or indirect subclass of the "cosmic" superclass Object
. Because it's such a superclass, Object
has few methods, and they're very general. The most important to know include:
.toString()
.equals(Object anotherObject)
, andObject clone()
10.5.1. The .toString()
method
The .toString()
method returns a string representation for each object. Calling it for for a rectangle looks like this:
Rectangle box = new Rectangle(5, 10, 20, 30); String s = box.toString(); System.out.println(x); java.awt.Rectangle[x=5,y=10,width=20,height=30]
Calling the .toString()
method for an object like a BankAccount
for which it hasn't been overridden will reveal the name of the class and a hash identifier: BankAccount@d24606bf
, or something like that. The Object
class doesn't know the particulars of our BankAccount, so if we want more useful information like what was given for the Rectangle
object above, we need to override the .toString()
method. The standard is to give:
- the name of the object
- open square bracket
- the name of the instance variables = the values of those variables, separated by commas, with no spaces
- closing square bracket
Creating .toString()
methods for your classes is a great tool for debugging. If you have any problems, it's one of the first strategies that you should consider for figuring out what's going on with your code.
Note that if subclasses extend your method then the .toString()
method that you've created won't give you as much detail as you might like.
With BankAccount, if you had done this before:
public String toString() { return BankAccount[balance=" + balance + "]"; }
you should instead use this:
public String toString() { return getClass().getName() + "[balance=" + balance + "]"; }
This will automatically substitute in the name of whatever class (or subclass) is currently being analyzed.
In the subclass, you'll need to grab out whatever instance variables were in your subclass too:
public class SavingsAccount extends BankAccount { public String toString() { return super.toString() + "[interestRate=" + interestRate + "]"; } . . .
10.5.2. The .equals(Object anotherObject)
method
Recall that we can't use the boolean operator ==
to compare two objects—that operator checks to see whether the two references point to the same object, and not whether they have the same value, which is usually what we want to be able to do.
We've seen how the value of two String
objects can be compared using the .equals()
method:
if (theName.equals("Richard")) { .... }
This works because the String
class has overridden the Object
superclass's .equals()
method. If you want to do the same thing with your own objects, you'll have to write your own .equals()
overriding method.
.equals()
with the Coin
class
public class Coin { ... public boolean equals(Object otherObject) // This must be of type Object! { Coin other = (Coin) otherObject; // casting the otherObject as a Coin return name.equals(other.name) && value == other.value; } ... }
Note that the parameter for the .equals()
method must be of the type Object
if we want to override the superclass's method. Otherwise, we're simply writing a new class that doesn't override the Object
's method, which will cause all sorts of problems.
Another example:
.equals()
with the Location
class
With our MazeRunner robots, we established a Location
class, defined by x and y coordinates.
public class Location
{
private int x;
private int y;
public Location(int y, int x)
{
this.x = x;
this.y = y;
}
.
.
.
public boolean equals(Object otherObject)
{
Location other = (Location) otherObject;
return x == other.x && y == other.y;
}
}
10.5.3. The instanceof
Operator
We can store subclasses in superclass variables—SavingsAccount
objects can be stored in an ArrayList<BankAccount>
, for example.
This conversion happens automatically.
We sometimes need to be able to convert in the other direction. We might need to go through the list of BankAccount
objects and call .addInterest()
on each account in there. We can't just call .addInterest()
on every BankAccount
: that method doesn't exist for regular BankAccount
s and CheckingAccount
s. We need a way of checking to make sure we only call .addInterest
if it's a CheckingAccount
that we're looking at.
The instanceof
operator allows us to do this.
The instanceof
operator
The instanceof
Boolean operator returns true
if an object is a reference to the indicated class.
// loop to addInterest to all SavingsAccounts for (BankAccount account : bankAccounts) { if (account instanceof SavingsAccount) { // cast BankAccount from array as a SavingsAccount // so we can use methods on it. SavingsAccount sa = (SavingsAccount) account; sa.addInterest(); } }