Resources‎ > ‎Java Basics‎ > ‎

Inheritance

Overview

Previously, we saw how interfaces enable software reuse by allowing the programmer to specify the behavior that will be supported by any class that implements the interface. This is useful if a set of classes will all support the same behavior in different ways. However, it is often the case that a set of classes support some of the same functionality, but also some different functionality. They exhibit a hierarchical relationship.

+Consider a program to maintain a database of information about all faculty, staff, and students at USF.

It would be logical to define classes Faculty, Staff, and Student. The Faculty class might have a data member name, a data member idNumber, and a data member officeNumber, along with methods to access an modify each of those data members. The Staff class might have all the same data members and functions that the Faculty class has, along with a data member to maintain the number of vacation days the Staff member has taken. Finally, the Student class might contain a name and idNumber, along with a GPA.

Inheritance enables the programmer to take advantage of the hierarchical relationships inherent in software design. The programmer can define a base class (aka superclass or parent class) the defines some generic behavior. Unlike an interface, the base class can actually provide method implementations. Derived classes (aka subclasses or child classes) extend the base class; they inherit all of the functionality in the base class and may optionally define additional functionality specific to the child class. We say that a derived class has an "is-a" relationship with the base class.

+How would you use inheritance to redesign the USF database example?

All USF people have a name and idNumber. Therefore, we might define a base class USFPerson that has a name and idNumber, and provides access and modifier methods for that information. Faculty, Staff, and Student would extend USFPerson and provide additional functionality such as maintaining the GPA for the Student class and maintaining the vacation days for the Staff member.

Also, notice that the Staff and Faculty both have officeNumber data members. We might consider adding another level the hierarchy as follows:

USFPerson
-name
-idNumber

USFEmployee extends USFPerson
-officeNumber

Faculty extends USFEmployee
-classesTaught?

Staff extends USFEmployee
-vacationDaysTaken

Student extends USFPerson
-GPA

Syntax

Defining a Base Class

A base class looks like any other class you might write. A first cut at the USFPerson class might looks as follows:

public class USFPerson {
protected String name;
protected String id;

public USFPerson(String name, String id) {
this.name = name;
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

}

protected Visibility

Notice that we used a new keyword protected. Typically, we would have used the visibility modifier private to indicate that the data members name and id should only be accessible from within the class USFPerson. However, according to our design, we'll need to access the USFPerson data members in the subclasses Faculty, Student, and Staff. Making the data members public would violate the principle of encapsulation by making the data members accessible to any class. The modifier protected is more appropriate; a protected member can be accessed within the class, as well as from any derived class.

Defining a Derived Class

The derived class Student would look as follows:

public class Student extends USFPerson {
protected double gpa;

public Student(String name, String id, double gpa) {
super(name, id);
this.gpa = gpa;

}
public double getGpa() {
return gpa;
}
public void setGpa(double gpa) {
this.gpa = gpa;
}

}

extends

The keyword extends is used to indicate that a particular class is derived from another. Recall that a class may implement multiple interfaces, but it may only extend one other class.

super

Notice that we use the keyword super on the first line of the constructor. Recall that we often use the keyword this to indicate we want to access a particular member in this object. super allows the programmer to access members of the super class. In this case, super(name, id) invokes the super class constructor -- that is the constructor in the class USFPerson.

Method Invocation

+Which methods can be invoked on an object of type USFPerson? How about Student?

USFPerson person = new USFPerson("Jane Wu", "12-344-56");
Student student = new Student("Sally Smith", "12-342-95", 3.45);
person.getName();
student.getName();
person.getGpa(); //invalid
student.getGpa();

More Concepts

Overriding

In some cases, you may want to reimplement methods from the base class in the derived class. For example, we might implement a method display in the class USFPerson as follows:

public void display() {
System.out.println("\tName: " + name);
System.out.println("\tID Number " + id);
}

On a Student object, we can certainly invoke this method. However, when displaying a Student object, we might want to display not only the student's name and id, but also her GPA. Therefore, we can override the display method of the USFPerson class by simply implementing the following method in Student.

public void display() {
System.out.println("\tName: " + name);
System.out.println("\tID Number: " + id);
System.out.println("\tGPA: " + gpa);
}

Invoking the display method on a Student object will invoke the Student display method.

Partial Overriding

Notice that both display methods print the name and id number from the object. Partial overriding enables us to avoid duplicating code by invoking the superclass version of a method from the subclass using the super keyword. To leverage partial overriding, we would modify the Student display method as follows:

public void display() {
super.display();
System.out.println("\tGPA: " + gpa);
}

Shadowing Variables

It is possible to declare a variable in the superclass and declare a variable with the same name in the subclass. For example, we could declare a variable name in the class Student. However, it is strongly advised that you do not use this approach.

final

The keyword final can be applied to variables, methods, and classes.

  • Final variables are constant. The value of a final variable cannot change.
  • Final methods cannot be overriden. This ensures that the functionality defined in a method remains the same in any derived class.
  • Final classes cannot be extended.

abstract

abstract classes

In some cases, it will not make sense to ever create an instance of a base class. In our example, all people associated with USF are either Faculty, Staff, or Students, so it actually does not make sense to ever have an instance of USFPerson. To enforce this property, we can declare the USFPerson class to be abstract. This simply means that the class cannot be instantiated.

To make USFPerson abstract, we would modify the class definition as follows:

public abstract class USFPerson {
//class definition here
}

With USFPerson now abstract, the following piece of code would result in a syntax error: USFPerson person = new USFPerson("Jane Wu", "12-344-56");

abstract methods

Recall that interfaces allow the programmer to specify that a derived class must implement a particular method, but the interface itself only specifies the method header. Similarly, abstract methods are simply method headers defined in an abstract superclass that must be implemented by a derived class. Note that an abstract method can only be specified in an abstract class.

To add an abstract method getEmailQuota to the abstract USFPerson class, we would simply add the following line to the class definition:

public abstract double getEmailQuota();

Polymorphism

Polymorphism means many forms.

A polymorphic reference is a reference variable that can refer to different types of objects at different points in time.

We saw an example of this when we discussed interfaces. We can have a variable of type Comparable that refers to an object of any class that implements the Comparable interface. Similarly, if a variable is of the type of a particular base class, it can refer to an instance of any class that is derived from that base class. Following is an example:

USFPerson student = new Student("Sally Smith", "12-342-95", 3.45);
USFPerson faculty = new Faculty("John Alexander", "14-392-26", "HR 544");
USFPerson[] persondb = new USFPerson[10000];
persondb[0] = new Student("Arnold Tang", "16-837-98");
persondb[1] = new Staff("Lucy Gomez", "83-384-92", 0);

Note that we can certainly have a variable of type USFPerson even if USFPerson is an abstract class. In the preceding example, we never create an instance of USFPerson.

Dynamic/Late Binding

Commonly, a programmer will use inheritance as above to maintain a collection of items that derive from a common base class. Often, the programmer will want to iterate through the collection and invoke a method, such as display, that is defined in the base class. That can easily be accomplished as follows:

for (int i = 0; i < dbSize; i++) {
persondb[i].display();
}

However, recall that we defined a method display in the class USFPerson, and we partially overrode the method display in Student. So, which version of the method is called in the preceding piece of code? You might guess that the USFPerson display method would be called since the method is called on a reference of type USFPerson. However, dynamic or late binding, used by Java, refers to the fact that Java determines which version of an inherited method is called by evaluating the contents of the object, not the reference.

Casting

Even though Java uses late binding, it is not possible to invoke a subclass (non-inherited) method on a reference of the superclass type. For example, the following code would result in a syntax error:

USFPerson student = new Student("Sally Smith", "12-342-95", 3.45);
student.getGpa(); //invalid

In order to invoke the method getGpa on a reference of type USFPerson, we would first need to cast the reference to be of type Student and then invoke the method:

USFPerson student = new Student("Sally Smith", "12-342-95", 3.45);
Student s = (Student)student
s.getGpa();
((Student)student).getGpa(); //alternative, also valid

If you have not taken particular care to ensure that the object you are casting is of the appropriate type, you may get a runtime exception, the ClassCastException. In the preceding example, this would have occurred if you had tried to cast the variable student to be a Faculty or Staff. To avoid this, you should use the instanceof operator as described below.

Often, we might want to iterate through a collection of elements of some base class and invoke a subclass method only on the objects of the particular subclass. To accomplish this, we use the instanceof operator to determine the type of a particular object and then cast the objects appropriately, as shown below:

for(int i = 0; i < dbSize; i++) {
if(persondb[i] instanceof Student) {
((Student)persondb[i]).getGpa();
}
}

Example

Assuming the following class structure, which of the following lines of code would result in an error? Which version of each method would be invoked?

Class1      Class2 extends Class1
-method1 -method2
-method2 -method3
Class1 c1 = new Class2();
Class2 c2 = new Class2();
c1.method3();
c1.method2();
c2 = (Class2)c1;
c2.method3();

The Object Base Class

Every Java class implicitly derives from the base class Object. Object provides several important methods that your classes inherit and may override. The two that you've used most frequently thus far are equals and toString.

Comments