Inheritance
In this chapter, we will study inheritance, which is an object-oriented concept that allows us to represent hierarchies.
For example, you might want a class to represent a person. However, you might also want to represent
students and professors. Well, all students are also people and all professors are also people. It
would be nice to store the more general information (like a person’s name and age) in a Person
class, and then create Student and Professor classes that can use the name and age from
Person, and then define more specific features.
Subsections of Inheritance
Parent Classes
A general class in Java is called a parent class. (It may also be referred to as a super class or
a base class). For example, a class that defines a general person might look like this:
public class Person
{
protected String name;
protected int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void print()
{
System.out.println("I am a person.");
}
}
This class looks pretty typical except for the “protected” keyword. Protected is another
visibility modifier (like private and public), but it’s specifically for inheritance. A
protected variable or method is one that is only visible inside the class or by any child
classes. (So, if we decide to make a more specific type of person, we will be able to see the
name and age.)
Child Classes
A child class is more specific version of a parent class. (A child class may also be called a sub
class or a derived class.) It retains the same features of the parent class, but adds more
detail/functionality. For example, a Student class would retain the name and age from the
Person class, but would add more features unique to students – major and gpa, for example.
To create a child class, you need to extend the parent class. Here’s the format:
public class ChildName extends ParentName
{
//normal class stuff
}
The child class automatically inherits any public or protected variables and methods from the
parent class. So if I do:
public class Student extends Person
{
}
Then the Student class inherits the name variable, the age variable, and the print method.
This means that we can pretend as if name, age, and print() are in the same class as
Student – we automatically get to use them, but we don’t have to write them again. So, we
only need to declare the major and gpa variables:
public class Student extends Person
{
private String major;
private double gpa;
}
Next comes the constructor of the child class. We could simply do:
public Student(String n, int a, string m, double g)
{
this.name = n; //Student inherits name
this.age = a; //Student inherits age
this.major = m;
this.gpa = g;
}
However, if you try this, the Student class will not compile. The reason for this is the
compiler will try to call the parent constructor (Person) before executing the code in the
Student constructor. It will try to call Person(), but of course Person does not have a no-argument constructor. To deal with this, we need to call the parent constructor ourselves. Here’s
how:
The super keyword refers to the parent object (similarly to “this””. This calls the Person
constructor and passes the name and age. Of course, the Person constructor initializes the
name and age variables itself, so now we don’t have to. Here’s what the Student constructor
looks like now:
public Student(String n, int a, String m, double g)
{
super(n, a);
this.major = m;
this.gpa = g;
}
Always include a call to super on the first line of the child constructor.
Creating Variables
Suppose we have the Person and Student classes from earlier in the chapter:
public class Person
{
protected String name;
protected int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void print()
{
System.out.println("I am a person.");
}
}
public class Student extends Person
{
private String major;
private double gpa;
public Student(String n, int a, String m, double g)
{
super(n, a);
this.major = m;
this.gpa = g;
}
}
We can create Person objects just as we’ve done before. For example:
Person p = new Person("Bob", 26);
We can also create new Student objects as we’ve done before:
Student s = new Student("Lisa", 18, "PSYCH", 3.25);
We can also call the print method for both of these variables. (Student inherits print()
because it extends Person.)
Both of these method calls print “I am a person.”
Since every Student is also a Person (because Student extends Person), a Student
can also be stored in a Person variable:
Person ps = new Student("Fred", 20, "ECE", 3.1);
However, not all people are students, so we can’t store a Person in a Student variable:
//NO! This will not compile - not all people are students.
Student bad = new Person("Jane", 30);
Method Overriding
Suppose again that we have the Person and Student classes from earlier in the chapter:
public class Person
{
protected String name;
protected int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void print()
{
System.out.println("I am a person.");
}
}
public class Student extends Person
{
private String major;
private double gpa;
public Student(String n, int a, String m, double g)
{
super(n, a);
this.major = m;
this.gpa = g;
}
}
Currently, when we call print with a
Student variable, “I am a person” is printed. However, maybe we want to print “I am a
person” for objects of type Person, and print “I am a student” for objects of type
Student. I can accomplish this by overriding the print method to indicate that we don’t want to
use the inherited method, but would instead like to create our own version of that method.
To do this, I just create another print method in the Student class:
public void print()
{
System.out.println("I am a student.");
}
When overriding a method, the version in the child class must have exactly the same header as
the version in the parent class. This means the same return type, name, and arguments.
Now, when we call print with a Student object, it will print “I am a student.” For example:
Student s = new Student("Lisa", 18, "PSYCH", 3.25);
Person ps = new Student("Fred", 20, "ECE”", 3.1);
s.print(); //prints "I am a student."
ps.print(); //prints "I am a student."
Object Class
Java defines the class Object, which is a parent of every other class. Every class that you
write (and every other class in the Java library) extends Object automatically. In fact, even
primitive variables like ints and chars can be treated as objects. (Primitive variables are NOT
objects, but they can be treated as such because of a feature called auto-boxing. This turns an int
into an Integer object, a double into a Double object, etc.)
For example, we can store all kinds of values in an Object variable:
Object val1 = 4;
Object val2 = "hello";
//Assumes we have our Person class from the previous section
Object val3 = new Person("Bob", 25);
This is handy when we want to define classes that can store general types of data. If we let them
store objects, then they can hold any kind of values. All objects can use the method equals,
which compares if two objects are the same. They also all have the method toString, which
converts the object into a string.
instanceof
With inheritance, we can do something like this (assuming Person and Student were as defined earlier in the chapter):
Person p = null;
if (condition)
{
p = new Person("Bob", 24);
}
else
{
p = new Student("Bob", 24, "ECE", 3.2);
}
Here, we either store a Person object OR a Student object in p, depending on some
condition. It might be nice to have a way to tell which type of object we’ve stored. We can do
this with the instanceof command. Here’s how:
if (p instanceof Student)
{
//if we want, convert to a Student variable
Student s = (Student) p;
}
This condition will evaluate to true if we stored a Student object inside p. We may then want
to cast p to a Student variable, as we did above.
Calling Methods in Parent Class
Sometimes we may want to call the parent version of a method from the child class. As an example, suppose agian that we have the Person and Student classes from earlier in the chapter:
public class Person
{
protected String name;
protected int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void print()
{
System.out.println("I am a person.");
}
}
public class Student extends Person
{
private String major;
private double gpa;
public Student(String n, int a, String m, double g)
{
super(n, a);
this.major = m;
this.gpa = g;
}
}
Suppose we wanted our Student print method to FIRST print “I am a person” and THEN print “I
am a student”. We could do this with two print statements, or we could call the print method
for Person from inside the Student print method. Here’s the format for calling a parent method
from inside the child class:
So, here is our revised print method for Student:
public void print()
{
//Will go to Person's print method, and print "I am a person."
super.print();
System.out.println("I am a student.");
}