AP Computer Science

Unit 10, part 1: Inheritance

Topics covered in this unit

By the time you are done with this unit you will:

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!

  1. Hierarchies: Superclasses and subclasses
  2. Inheriting Instance Fields and Methods
  3. The Question Class
  4. Abstract Classes
  5. 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?

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:

  1. You can inherit methods from the superclass. If you don't override the superclass, you automatically inherit the methods of the superclass.
  2. 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).
  3. 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:

  1. A subclass can never override instance fields.
  2. 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.
  3. 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?

Show/hide source code

/** * 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?

  1. The object will have to store a number of possible responses for the question.
  2. The object will need a method for adding those multiple answer choices.
  3. 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:

  1. The object will have to store multiple responses for the question.
  2. The object will need a method for adding multiple answer choices.
  3. 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:

  1. Write the complete ChoiceQuestion class.
  2. Write a QuestionDemo2 class that:
    1. creates a two question multiple-choice quiz, and
    2. 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 Questions. Does it also work with ChoiceQuestions? 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:

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:

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:

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 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.

// 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(); } }