CMUQ 15-121 Inheritance
- 1. Introduction
- 2. Doing Things the Hard Way
- 3. Doing Things the Better Way
- 4. Terminology and Limitations
- 5. Superclass and Subclass Compatibility
- 6. Superclasses and Subclasses in Lists
1. Introduction
Inheritence 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.
2. Doing Things the Hard Way
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, in fact, 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, in fact, 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.
3. Doing Things the Better Way
Remember, we previously noticed that a student is-a person. Given this, we can actually tell Java this and have the Student
class automatically receive data and code from the Person
class. 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 actually 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; } }
4. Terminology and Limitations
Let’s take a look at some terminology around inheritance so we have common vocabulary to talk about it:
- Superclass: The parent class that is extended. (In this case,
Person
is the parent or superclass.) - Subclass: The child that inherits from the parent. (In this case,
Student
is the child or subclass.)
There also some limitations when using inheritance:
- A class can only inherit from one other class.
- Depending on the visibility modifiers, a subclass might not be able to directly access variables or methods of
the super class. If something is declared
public
orprotected
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.
5. Superclass and Subclass Compatibility
In a program, subclasses can be stores 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 subclass.
For example:
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.
6. Superclasses and Subclasses in Lists
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. In order to access subclasses as subclasses, you’ll need to cast. Here’s an example illustrating all of this:
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 actually a Student if (p instanceof Student) { /* * Since they are, we can cast the Person to Student. This allows use 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"); } }