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
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:
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.
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 a SavingsAccount, and not with the superclass BankAccount).
- 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 than BankAccount.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 field balance. 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 the interestRate of a SavingsAccount.
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?
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:
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:
So, what does the code look like for implementing this method?
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:
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:
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.
This code works with Questions. Does it also work with ChoiceQuestions? Could we do this?
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:
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:
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:
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
Show/hide hint for writing the Rectangle class
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), and
- Object 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:
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:
you should instead use this:
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:
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:
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
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.
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 BankAccounts and CheckingAccounts. 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.