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:

super(n, a)

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

p.print();
s.print();

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:

super.methodName(args)

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.");
}