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.
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?
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.
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.
+Which methods can be invoked on an object of type USFPerson? How about Student?
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); }
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.
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 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.
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.
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();
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.
Date: 2007-09-17