9. 1001 - Inheritance
What’s the Point?
-
Design classes using inheritance
-
Use the
extends
keyword to create a subclass -
Override methods in a subclass
-
Implement constructors in a subclass
-
Use polymorphism to create arrays of subclass objects
Source code examples from this chapter and associated videos are available on GitHub.
9.1. Inheritance Overview
Inheritance is a core object-oriented programming concept that allows us to reuse code from one class—known as a parent class--in a new class, which we call a child class. In the same way that people can inherit traits from their parents, child classes inherit the attributes and behaviors of their parent classes. This means that a child class automatically has all of the fields and methods of a parent class, and just like people, a child class can also have its own unique fields and methods.
Though there are other ways to think about inheritance, I encourage students to think of it as a way to create a class that is a more specific version of another class. For example, if we have a class called Athlete
, we can create a child class called FootballPlayer
.
A FootballPlayer
is a type of Athlete
, and there might be other types of athletes that are not football players: gymnasts, tennis players, and so on.
We can think of inheritance as an "is-a" relationship: a House is a Building , for example. When deciding whether to use inheritance, ask yourself if the child class is a type of the parent class. We call this the is-a test.
|
When we design classes in UML, we use an arrow to indicate inheritance. The arrow points from the child class to the parent class.

Athlete
parent class with two child classes, FootballPlayer
and TennisPlayer
In this example, the FootballPlayer
class inherits the fields and methods of the Athlete
class, so we don’t have to rewrite those, and then it adds on its own unique fields and methods, like teamName
and position
.
9.2. Implementing Inheritance
When we move from a design in UML to writing actual code—which we refer to as implementation--we use different terms to describe the relationships between classes.
A parent class becomes a superclass, and a child class becomes a subclass. You may also hear the terms base class and derived class to describe the same relationship, especially in other programming languages.
In Java, we use the extends
keyword to indicate that one class is a subclass of another class. Here’s an example of a FootballPlayer
class that extends the Athlete
class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Athlete {
private String athleteName;
private int age;
private int weight;
public void run() {
System.out.println("Running!");
}
public void jump() {
System.out.println("Jumping!");
}
public void throwBall() {
System.out.println("Throwing!");
}
}
public class FootballPlayer extends Athlete {
private String teamName;
private String position;
public void tackle() {
System.out.println("Tackling!");
}
public void pass() {
System.out.println("Passing!");
}
}
9.3. Inheritance Hierarchies and Overriding
When we extend a class, we are building an inheritance hierarchy.
This is a tree-like structure where each class has only one parent class (such as our Athlete
class), but can have multiple child classes (like FootballPlayer
and TennisPlayer
).

Athlete
classWhen a method is called on an object, Java begins by looking for that method in the object’s class. If it doesn’t find the method there, it looks in the superclass. If there’s no method in there, it goes to that class’s superclass, and so on up the hierarchy. If it gets to the top of the hierarchy and still hasn’t found the method, the code will not compile.
1
2
3
TennisPlayer serena = new TennisPlayer();
serena.serve(); (1)
serena.run(); (2)
1 | The compiler checks the TennisPlayer class for the serve() method. Since it finds an implementation there, it will execute that code. |
2 | The compiler checks the TennisPlayer class for the run() method. Since it doesn’t find an implementation there, it will check the superclass, Athlete . Since it finds the run() method there, it will execute that code. |
In some cases, we might want to provide a different implementation of a method in a subclass. This is called method overriding. Because the compiler starts at the bottom of the inheritance hierarchy and works its way up, it will use the overridden method in the subclass instead of the method in the superclass.
If a TennisPlayer
has a specific way of running that is different from the way an Athlete
runs, we can override the run()
method in the TennisPlayer
class.
The FootballPlayer
class will still use the run()
method from the Athlete
class, unless we override it in the FootballPlayer
class as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TennisPlayer extends Athlete {
private String dominantHand;
private int rank;
public void serve() {
System.out.println("Serving!");
}
public void volley() {
System.out.println("Volleying!");
}
@Override
public void run() {
System.out.println("Running like a tennis player!");
}
}
The @Override annotation is optional, but it’s a good idea to use it. It tells the compiler that you intend to override a method from the superclass. If you make a mistake in the method signature, the compiler will let you know.
|
9.3.1. The Object
Class
In Java, every class is a subclass of the Object
class.
This means that every class that does not explicitly extend another class is a subclass of Object
.
The result is that Object
is at the top of the inheritance hierarchy for all Java classes—and every object inherits the methods in the Object
class.
The Object
class provides a handful of methods, but there is one that is particularly useful at this stage of our learning: the toString()
method.
This method returns a String
representation of the object.
The compiler will automatically call the toString()
method when we try to print an object:
1
2
3
TennisPlayer serena = new TennisPlayer();
System.out.println(serena);
// Compiler changes this to: System.out.println(serena.toString());
Since TennisPlayer
does not have an implementation of the toString()
method, the compiler will work up the inheritance hierarchy until it finds an implementation in the Object
class.
The default implementation of toString()
in the Object
class returns a string that includes the class name and the memory address of the object, which looks something like this: TennisPlayer@15db9742
.
This is probably not very useful to us, but we can override the toString()
method in our TennisPlayer
class to provide a more meaningful representation of the object.
A common practice is to return a string that includes the values of the object’s fields.
toString()
method in the TennisPlayer
class1
2
3
4
5
6
7
@Override
public String toString() {
return "TennisPlayer{" +
"dominantHand='" + dominantHand + '\'' +
", rank=" + rank +
'}';
}
Now when we print a TennisPlayer
object, we will see a string that looks something like this: TennisPlayer{dominantHand='right', rank=1}
.
However, we can return any information we want in the toString()
method, so we can customize it to fit our needs.
The toString() method is used in many places in Java, so it’s a good idea to override it in your classes to provide a more meaningful representation of your objects.
|
The Object
class also provides other methods, such as equals()
, which is used to compare two objects for equality.
For example, we use the equals()
method when we check if two strings are the same, like this: if (bestSchool.equals("EMCC"))
.
The Lab Assignments in Canvas can be completed using what we’ve covered to this point. You might choose to complete that work now, then move onto the next section—which you’ll need for the Programming Project. |
9.4. Constructors & Inheritance
As we’ve seen, when we create an object of a class, Java automatically calls the class’s constructor to initialize the object. When we create an object of a subclass, Java will call the constructor of the superclass first, and then the constructor of the subclass. Remember that the compiler will create default constructors for us if we don’t provide any, but if we do provide a constructor, the compiler will not create a default constructor. Things are pretty straightforward when using default or parameterless constructors.
However, if we provide a constructor with parameters in the superclass, we need to make sure that the subclass constructor calls the superclass constructor and provides values for the parameters.
To explicitly call the superclass constructor, use the super
keyword.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Athlete {
private String athleteName;
private int age;
private int weight;
public Athlete(String athleteName, int age, int weight) {
this.athleteName = athleteName;
this.age = age;
this.weight = weight;
}
}
public class FootballPlayer extends Athlete {
private String teamName;
private String position;
public FootballPlayer(String athleteName, int age, int weight, String teamName, String position) {
super(athleteName, age, weight); (1)
this.teamName = teamName;
this.position = position;
}
}
1 | The super keyword is used to call the superclass constructor. Since the Athlete class constructor requires three parameters, we need to provide values for those parameters here. |
The superclass constructor call must be the first statement in the subclass constructor, so we can’t have any other code before it. If we don’t provide a call to the superclass constructor, Java will automatically call the superclass’s default/parameterless constructor, which may not be what we want.
9.5. Introduction to Polymorphism
Polymorphism is a powerful concept in object-oriented programming that allows us to treat objects of different classes as if they were objects of a common superclass. It can be difficult for inexperienced programmers to recognize all of the ways that polymorphism can be used, but one of the most common uses is to create arrays of objects of different subclasses.
This means that we can create an array of Athlete
objects, and we can use it to store objects of the Athlete
class, the FootballPlayer
class, and the TennisPlayer
class.
This is possible because every FootballPlayer
is an Athlete
, and every TennisPlayer
is an Athlete
.
1
2
3
4
Athlete[] athletes = new Athlete[3];
athletes[0] = new Athlete("Alice", 25, 150);
athletes[1] = new FootballPlayer("Bob", 30, 200, "Broncos", "Quarterback");
athletes[2] = new TennisPlayer("Charlie", 20, 175, "right", 1);
When we access an object in the array, we can only use the methods that are available in the Athlete
class (unless we use something called casting, which is beyond our scope here).
This means that we can call the run()
method on any object in the array, but we can’t call and any subclassclass methods like tackle()
in FootballPlayer
or serve()
in TennisPlayer
.
Check Yourself Before You Wreck Yourself (on the assignments)
Can you answer these questions?
Sample answers provided in Stuff That’s Tacked On The End.