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
.