2-10 Inheritance

The basic idea of inheritance in Object-Oriented Programming is that we can define a new (“child”) class based on an existing (“parent”) class. The child class is similar to the original parent class but adds more specialized data and methods to make the new class unique in some way. As you will learn, inheritance is a very powerful code reuse technique that not only reduces duplicate code but also simplifies overall program design.

Defining a Person Class

Let’s start with a simple class to represent a Person.

There’s nothing in the code above that you haven’t seen before. Observe the following interaction:

> Person p = new Person(“Vik-20”)
> p.getName()
“Vik-20”
> p.setName(“James Bond”)
> p.getName()
“James Bond”
> p.toString()
“Person@5288d319”

In the interaction above, I instantiate a Person object (p) and then proceed to demonstrate the accessor and mutator methods that I defined in the Person class. But notice the call to toString(). This does not cause an error even though this method was not defined in our Person class. Somehow the toString() method exists and returns a String containing the type (class name) of the p object and it’s memory location. Where did this toString() method come from? Inheritance!

If you look at the Java API there is a class called Object. It turns out that every class in Java inherits the methods from the Object class. This means that without having to cut-and-paste any code, we are able to call the methods defined by the Object class within our Person class. This is why inheritance is such a powerful code reuse technique.

Method Overriding

Going back to the example above, the Object class is the parent class (also called a superclass), and the Person class is the child class (also called a subclass).

The inherited behaviour of the toString() method from the Object class is not very useful, so let’s define our own toString() method in the Person class:

Now we can get more useful information about a Person object:

> Person p = new Person(“Vik-20”)
> p.toString()
“Person: Vik-20”

In 2-1 we talked about method overloading where we have two (or more) methods with the same names but different parameters. In this case, the toString() method that I have defined in the child class has exactly the same method signature as the toString() method inherited from its parent class (Object).  This is called method overriding. You may override any method inherited from a parent class.

One way of thinking about method overriding is like plastic surgery. Imagine you have inherited your father’s nose but would really prefer a different one. No problem! With modern medicine you can surgically replace (i.e., override) the nose you inherited with one more to your liking!

The @Override Annotation

The toString() method is commonly overridden but the fact that a method overrides one from a parent class is not always immediately obvious. You would be correct in thinking that this would be a good thing to note in the header comment for the overriding method, but the preferred way in Java is to include the annotation @Override on the line before the method signature, like so:

Like commenting, annotations provide information about code.  To anyone reading your code it will be immediately obvious that this method overrides an inherited method.

Unlike comments, annotations are also helpful to the Java compiler!  For example, in the code above if we had misspelled toString() as tooString() the Java compiler will recognize that there is no tooString() method in the Object superclass and report this as a syntax error. Without @Override the Java compiler would assume that you meant to create a unique tooString() instance method and the inherited toString() method would not get overridden.

Defining a Student Class

Now let’s define a class for a specific kind of Person called a Student:

If you think about it, a Student is a specific kind of Person. In other words, a Student object should have the same data and methods as a Person does but with some data (e.g., student id) and methods that are unique to students.

Rather than accept the default of inheriting from the Object class, on line 1 is the keyword extends followed by the class name Person. This tells the Java compiler that the Student class should inherit from the Person class instead. The result of this is that any Student object can call methods defined in the Student class but also methods defined in the Person class. Furthermore, the Student class also inherits all of the methods from the Object class since the Person class inherited those!

Let’s interact with the Student class:

> Student s = new Student( “James Bond”, 123456789 )
> s.getStudentId()
123456789
> s.getName()
“No Name Given!”
> s.toString()
” is a Student with id #123456789″

The super Keyword

Pretty good, you can see that we are able to call the getName() method inherited from the Person class but the name we are passing into the Student constructor does not seem to be getting set properly.

Because name is declared as private in the Person class, it can only be accessed within the Person class code and not outside, including child classes like Student. So if we try to access it directly with the Student class like this, line 2 we will cause a compiler error:

Proper encapsulation is very important in OOP, so the fact that name is private in Parent is not the problem. Actually, it’s good design! There are two ways to deal with this. One way is that we could call the public setName() mutator method inherited from Parent, like so:

This works, but we are not always guaranteed to have a mutator method for every private instance variable in a parent class. The best way is to call a constructor of the parent class to set private instance variables. To do this, we use the super keyword:

Line 2 above calls the parameterized constructor from the Person class that will set the name. The super keyword is special like the keyword this except it refers to the parent class rather than the current object. The call to super() must always be the first line in a child class constructor.

You may have noticed in our original version of the Student class that the no-argument version of the Person constructor seemed to be getting called automatically to set the name instance variable to “No Name Given!”. The rule is: If you do not use the super keyword to call a parameterized constructor from the parent class, then Java automatically calls the no-argument (or default) version for you. In other words, you can imagine that it automatically adds line 2 (below) to your constructor.

We can also use the super keyword to improve the toString() method in the Student class:

The Student class overrides the toString() method inherited from the Person class (which in turn overrode it from the Object class). By prefacing it with super. we can still call the overridden version of toString() from the Person class. This way we can combine the result from the parent toString() method with the toString() result of the Student class. We now have an improved Student class:

> Student s = new Student( “James Bond”, 123456789 )
> s.getName()
“James Bond”
> s.toString()
“Person: James Bond is a Student with id #123456789”

Inheritance in UML

Here is how we would represent the Person and Student classes, and their inheritance relationship in UML:

2-10-umlclassdiagram

Because Student inherits from Person, we draw an arrow pointing from the Student class to the Person class. In UML, child classes always point to the parent, not the other way around. The Object class is specific to Java and not normally included in UML diagrams.

You Try!

  1. Create your own class that inherits (extends) the Person class, and a test program to demonstrate it.
  2. Let’s use inheritance to refine your Rectangle and Oval classes!  You probably noticed already that these two classes have a great deal in common; for example, they each have 5 similar instance variables: x1, y1, x2, y2, and filled. They also have many common methods.  Create a new parent class called FillableShape that your Rectangle and Oval classes will inherit from.  Move any common instance variables and methods from your Rectangle and Oval classes into their FillableShape parent class.  When you are done, your Rectangle class should be left with 1 private static class variable, 1 no-argument constructor, 1 parameterized constructor, and 4 public methods that are unique to Rectangle objects: getNumRectangles(), calcArea(), isOverlapping(), and toString().  Don’t forget to use the super keyword in your parameterized constructor.  The instance variables and methods left in your Oval class should be similar to those in your improved Rectangle class.
  3. Draw a complete UML class hierarchy diagram, like the one shown for Person and Student.