Inheritance is a convenient method of sharing code and data between related classes. When one class inherits from another, we say there is an is-a relationship between them. When one class inherits from another, it receives copies of relevant instance variables and methods without you needing to rewrite the code for them.
In these notes, we’ll go over the basics of inheritance by using a simple example.
Imagine that you are working on a Java program and you need a class to store some basic information about a person, such as their name and age. You would likely come up with something like this:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void birthday() {
this.age++;
}
public String toString() {
return this.name + " (Age: " + this.age + ")";
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
It is a simple class with a basic constructor, a birthday method to change the age, a toString
, and a few getter methods for the name
and age
.
Now, later on, while extending the program you also need a class to store information about a student. In your mind, a student is-a Person, so they will have a lot in common. So much so that you find yourself copying a lot of code from Person
into your new Student
class:
public class Student {
private String name;
private int age;
private String major;
public Student(String name, int age, String major) {
this.name = name;
this.age = age;
this.major = major;
}
public void birthday() {
this.age++;
}
public String toString() {
return this.name + " (Age: " + this.age + ")" + " (Major: " + this.major + ")";
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public String getMajor() {
return this.major;
}
}
The code is so similar that this is basically the Student
class with just a little bit of extra code added to handle the major.
Situations like this come up a lot in development, and there is a much better way to handle it than simply duplicating code.
Remember, we previously noticed that a student is-a person. Given this, we can tell Java this and have the Student
class automatically receive data and code from the Person
class. To do this we use the extends
keyword. Let’s take a look:
public class Student extends Person {
private String major;
public Student(String name, int age, String major) {
super(name, age);
this.major = major;
}
public String toString() {
return super.toString() + " (Major: " + this.major + ")";
}
public String getMajor() {
return this.major;
}
}
This code is much smaller but is functionally identical to the previous Student
class we wrote. Here’s another version of the same thing, but with comments to talk you through it:
// Notice how we use "extends" to tell Java that Student should inherit from Person.
public class Student extends Person {
/*
* Here we declare a new instance variable, major, to store the major. We don't
* need to redeclare name and age; Student inherits those for free.
*/
private String major;
// We don't, however, inherit constructors. So we need to provide one.
public Student(String name, int age, String major) {
/*
* This next line is interesting. We can call the constructor from
* Person using super(), so we don't need to repeat that code.
*/
super(name, age);
/*
* But, that parent constructor doesn't initialize major for us, since that only
* exists here in Student. So we do that here.
*/
this.major = major;
}
/*
* We provide a new version of toString because we want ours to be different
* than the one in Person. Notice, however, that this toString() does call the
* toString() from Person and use its result.
*/
public String toString() {
return super.toString() + " (Major: " + this.major + ")";
}
/*
* We need our own getter for major because Person doesn't have one.
*/
public String getMajor() {
return this.major;
}
}
Let’s take a look at some terminology around inheritance so we have a common vocabulary to talk about it.
A superclass is the parent class that is extended. (In this case, Person
is the parent or superclass.)
A subclass is a child that inherits from the parent. (In this case, Student
is the child or subclass.)
There are also some limitations when using inheritance:
A class can only inherit from one other class. (In other words, a child can only have one parent.)
Depending on the visibility modifiers, a subclass might not be able to directly access variables or methods of the superclass. If something is declared public
or protected
then a subclass can access it directly. Otherwise, it can’t. In practice this isn’t a major limitation: You simply provide methods for getting and setting the instance variables that you want the subclass to be able to access.
In a program, subclasses can be stored in any place the superclass can. A variable of type T can hold an object of type T or any subclass of T. This means, for example, that a Person
variable can have a Student
object assigned to it. When this happens, however, you can’t access any methods or variables that only exist in the Student
.
Person p = new Student("John Smith", 19, "InfoSys");
System.out.println(p.getName());
System.out.println(p);
Will print out:
John Smith
John Smith (Age: 19) (Major: InfoSys)
However, you can’t do this:
Person p = new Student("John Smith", 19, "InfoSys");
System.out.println(p.getMajor());
In this case, the call to getMajor()
can’t happen because you’ve declared p
to be a Person
, and the Person
class doesn’t have a getMajor()
method. Even though the object stored in p
is a Student
, because the type of p
is Person
Java will not allow you to treat it like a Student
.
You can store superclasses and their subclasses into the same arrays or lists. When you pull the items out, however, they will all have the same type as the superclass. To access subclasses as subclasses, you’ll need to cast.
To cast a variable is to manually force Java to treat it as a different type.
Here is an example of casting:
ArrayList<Person> bigList = new ArrayList<Person>();
bigList.add(new Person("Bob Jones", 45));
bigList.add(new Student("John Smith", 19, "InfoSys"));
bigList.add(new Person("Bridget Johnson", 22));
bigList.add(new Student("Muna Al-Mannai", 21, "CompSci"));
for (Person p : bigList) {
System.out.println(p);
}
for (Person p : bigList) {
// Get the name of the person
String name = p.getName();
// Check if p is a Student
if (p instanceof Student) {
/*
* Since they are, we can cast the Person to Student. This allows us to call
* getMajor() on it.
*/
Student s = (Student) p;
String major = s.getMajor();
System.out.println(name + " is a Student with major " + major);
} else {
System.out.println(name + "is a Person");
}
}