Inheritance
Inheritance is the OOP property whereby a class can extend another class
(treat it as a template) in order to inherit its members (incorporate them without
explicit coding). This is indicated in the class’s declaration by the keywords:
extends otherclassname. The extended class is termed the
direct superclass, base class, or parent class. The extending
class is termed the direct subclass, derived class, or child
class. Its own explicitly coded members supplement or override its inherited
members.
A subclass may itself be extended by a further subclass. An inheritance
chain is a series of classes directly extending each other; within one, a
given class is a subclass of every class above it and a superclass of every one below
it. A subclass inherits every member (field or method) of every superclass.
A class whose declaration omits the extends keyword, nevertheless
implicitly extends the Object class. Therefore,
Object is the master superclass at the top of every inheritance chain
and every Java class inherits its members. Multiple subclasses may extend the
same superclass, but not vice-versa (there is no multiple inheritance). An
inheritance hierarchy is a pyramid structure of subclasses extending downward
from Object, and it includes one inheritance chain for each bottom node.
When an object is constructed, each of the fields declared in its class and chain of
superclasses is allocated a distinct memory location, regardless of whether the same
field name has been declared by multiple classes. However, a member name declared
in a given class hides the like-named member declared in the closest superclass.
The visibility of this distinct member extends downward through all subclasses until
the next one that hides it by declaring the same name. To expose the closest
hidden member, a class can append the prefix, super to the name.
Another, inconsistent purpose of super is to provide shorthand notation
for the constructor of a superclass. The subclass constructor, to call one of its
superclass’ constructors, can include as its first statement:
super(params), where (params)
matches the signature of the superclass constructor. Without this statement, the
subclass constructor will call the default superclass’ no-arg constuctor, by default.
The following examples demonstrate an inheritance chain of three classes:
Example 1 is class, TestChainBase,
the base class.
Example 2 is class, TestChainMid,
the direct subclass of the base class.
Example 3 is class, TestChainLow,
the lowest level subclass.
Example 4 is class, TestChain,
which demonstrates how data hidden by overloaded names can be exposed. Note how
the OOP property of Polymorphism assures that an object knows which overridden
method to invoke regardless of the type of the reference variable pointing to it.
The base class.
public class TestChainBase {
private int testPriv = 11; // instance variables
private int basPriv = 12; // with default values
public int testPub = 13;
public int basPub = 14;
public int getTestPriv() { // overridden by subclasses
System.out.println(" getTestPriv method of class TestChainBase");
return testPriv;
}
public int getBasePriv() { // class-specific get method
return basPriv;
}
public String toString() { // class-specific toString
return "Base: testPriv = " + testPriv +
", basPriv = " + basPriv +
", testPub = " + testPub +
", basPub = " + basPub;
}
}
The middle derived class.
public class TestChainMid extends TestChainBase {
private int testPriv; // instance variables
private int midPriv;
// this class omits testPub
public int midPub;
public TestChainMid(int tpriv, int mpriv, int mpub) {
testPriv = tpriv; // implied call to superclass'
midPriv = mpriv; // default no-arg constructor
midPub = mpub;
}
public int getTestPriv() { // overridden by subclasses
System.out.println(" getTestPriv method of class TestChainMid");
return testPriv;
}
public int getTestPrivUp1() { // return hidden testPriv
return super.getTestPriv(); // one level higher
}
public int getMidPriv() { // class-specific get method
return midPriv;
}
public String toString() { // class-specific toString
return super.toString() + "\n" // variables defined in superclasses
+ "Mid: testPriv = " + testPriv +
", midPriv = " + midPriv +
", " + " " + // testPub omitted
" midPub = " + midPub;
}
}
The lowest-level derived class.
public class TestChainLow extends TestChainMid {
private int testPriv; // instance variables
private int lowPriv;
public int testPub; // hides TestChainBase version
public int lowPub;
public TestChainLow(int tpriv, int lpriv, int tpub, int lpub) {
super(21, 22, 24); // call superclass constructor
testPriv = tpriv;
lowPriv = lpriv;
testPub = tpub;
lowPub = lpub;
}
public int getTestPriv() { // overrides superclass methods
System.out.println(" getTestPriv method of class TestChainLow");
return testPriv;
}
public int getTestPrivUp1() { // return hidden testPriv
return super.getTestPriv(); // one level higher
}
public int getTestPrivUp2() { // return hidden testPriv
return super.getTestPrivUp1(); // two levels higher
}
public int getTestPubUp1() { // return hidden testPub
return super.testPub; // one level higher - skip TestChainMid
}
public int getLowPriv() { // class-specific get method
return lowPriv;
}
public String toString() { // class-specific toString
return super.toString() + "\n" // variables defined in superclasses
+ "Low: testPriv = " + testPriv +
", lowPriv = " + lowPriv +
", testPub = " + testPub +
", lowPub = " + lowPub;
}
}
A program that displays all of the object’s data members.
public class TestChain {
public static void main(String[] args) {
TestChainLow low = new TestChainLow(31, 32, 33, 34); // create new obj
TestChainMid mid = low; // define subclass references
TestChainBase bas = low; // for later use
System.out.println("\nThe entire object:"); // display the object
System.out.println(low.toString() + "\n");
// display private variables with unique names
System.out.println("The unique private variables" +
" - calling class-specific get methods:");
System.out.println("basPriv = " + low.getBasePriv()
+ ", midPriv = " + low.getMidPriv()
+ ", lowPriv = " + low.getLowPriv() + "\n");
// display public variables with unique names
System.out.println("The unique public variables:");
System.out.println("basPub = " + low.basPub
+ ", midPub = " + low.midPub
+ ", lowPub = " + low.lowPub + "\n");
// display all versions of testPriv by calling
// super versions of same-named get methods
System.out.println("The same-named private variables" +
" - calling super of same-named get methods:");
System.out.println("Base testPriv = " + low.getTestPrivUp2()
+ ", Mid testPriv = " + low.getTestPrivUp1()
+ ", Low testPriv = " + low.getTestPriv() + "\n");
// references and casts to superclasses just access the lowest-level method
System.out.println("The same-named private variables - "
+ "This does NOT work!\n"
+ "The following always call the lowest-level"
+ " version of getTestPriv()\n"
+ "1) using subclass references:");
System.out.println("Base testPriv = " + bas.getTestPriv()
+ ", Mid testPriv = " + mid.getTestPriv()
+ ", Low testPriv = " + low.getTestPriv() + "\n");
System.out.println("2) using casts:");
System.out.println("Base testPriv = " + ((TestChainBase)low).getTestPriv()
+ ", Mid testPriv = " + ((TestChainMid)low).getTestPriv()
+ ", Low testPriv = " + low.getTestPriv() + "\n");
// display all versions of testPub one-by-one
System.out.println("The same-named public variables - This works!\n"
+ "1) using subclass references:");
System.out.println("Base testPub = " + bas.testPub
+ ", Low testPub = " + low.testPub + "\n");
System.out.println("2) using casts:");
System.out.println("Base testPub = " + ((TestChainBase)low).testPub
+ ", Low testPub = " + low.testPub + "\n");
System.out.println("3) calling methods that return super values:");
System.out.println("Base testPub = " + low.getTestPubUp1()
+ ", Low testPub = " + low.testPub + "\n");
}
}