|
JavaFAQ Home » Java Lectures by Anatoliy Malyarenko

Inheritance
by: Anatoliy Malyarenko
Abstract
- Contents of the lecture.
- Understanding inheritance.
- Overriding methods.
- Being a descendent of Object.
- Writing final classes and methods.
- Writing abstract classes and methods.
- Implementing nested classes.
Managing inheritance
Recall that the extends clause declares that your class is a subclass of another. You can
specify only one superclass for your class (Java does not support multiple class inheritance),
and even though you can omit the extends clause from your class declaration, your class
has a superclass. So, every class in Java has one and only one immediate superclass. This
statement leads to the question, "Where does it all begin?"
As depicted in the following figure, the top-most class, the class from which all other
classes are derived, is the Object class defined in java.lang.

The Object class defines and implements behaviour that every class in the Java system
needs. It is the most general of all classes. Its immediate subclasses, and other classes near
top of the hierarchy, implement general behaviour; classes near the bottom of the hierarchy
provide for more specialised behaviour.
Definition 1. A subclass is a class that extends another class. A subclass inherits state and
behaviour from all of its ancestors. The term "superclass" refers to a class's direct ancestor as
well as to all of its ascendant classes.
What members does a superclass inherit?
A subclass inherits all of the members in its superclass that are accessible to that
subclass unless the subclass explicitly hides a member variable or overrides a method. Note that constructors are not members and are not inherited by subclasses. The following list itemises the members that are inherited by a subclass:
- Subclasses inherit those superclass members declared as public or protected.
- Subclasses inherit those superclass members declared with no access specifier as long
as the subclass is in the same package as the superclass.
- Subclasses don't inherit a superclass's member if the subclass declares a member with
the same name. In the case of member variables, the member variable in the subclass
hides the one in the superclass. In the case of methods, the method in the subclass
overrides the one in the superclass.
Creating a subclass can be as simple as including the extends clause in your class
declaration.
However, you usually have to make other provisions in your code when
subclassing a class, such as overriding methods or providing implementations for abstract
methods.
Hiding member variables
As mentioned before, member variables defined in the subclass hide member variables
that have the same name in the superclass.
One interesting feature of Java member variables is that a class can access a hidden
member variable through its superclass. Consider the following superclass and subclass pair:
| Code: |
class Super {
Number aNumber;
}
class Subbie extends Super {
Float aNumber;
}
|
The aNumber variable in Subbie hides aNumber in Super. But you can access Super's
aNumber from Subbie with
super is a Java language keyword that allows a method to refer to hidden variables and
overridden methods of the superclass.
Overriding methods
The ability of a subclass to override a method in its superclass allows a class to inherit
from a superclass whose behaviour is "close enough" and then override methods as needed.
For example, all classes are descendants of the Object class. Object contains the
toString method, which returns a String object containing the name of the object's class
and its hash code. Most, if not all, classes will want to override this method and print out
something meaningful for that class.
Let's resurrect the Stack class example and override the toString method. The output
of toString should be a textual representation of the object. For the Stack class, a list of the
items in the stack would be appropriate.
| Code: |
public class Stack {
private Vector items;
// code for Stack's methods and constructor
//not shown
// overrides Object's toString method
public String toString() {
int n = items.size();
StringBuffer result = new StringBuffer();
result.append("[");
for (int i = 0; i < n; i++) {
result.append(items.elementAt(i).toString());
if (i < n-1) result.append(",");
}
result.append("]");
return result.toString();
}
}
|
The return type, method name, and number and type of the parameters for the overriding
method must match those in the overridden method. The overriding method can have a
different throws clause as long as it doesn't declare any types not declared by the throws
clause in the overridden method.
Also, the access specifier for the overriding method can
allow more access than the overridden method, but not less. For example, a protected
method in the superclass can be made public but not private.
Calling the overridden method
Sometimes, you don't want to completely override a method. Rather, you want to add
more functionality to it. To do this, simply call the overridden method using the super keyword.
For example,
| Code: |
super.overriddenMethodName();
|
Methods a subclass cannot override
A subclass cannot override methods that are declared final in the superclass (by
definition, final methods cannot be overridden). If you attempt to override a final method,
the compiler displays an error message similar to the following and refuses to compile the
program:
FinalTest.java:7: Final methods can't be overridden.
Method void iamfinal() is final in class ClassWithFinalMethod.
void iamfinal() {
^
1 error
Also, a subclass cannot override methods that are declared static in the superclass.
In other words, a subclass cannot override a class method. A subclass can hide a static
method in the superclass by declaring a static method in the subclass with the same
signature as the static method in the superclass.
Being a descendent of Object
The Object class sits at the top of the class hierarchy tree in the Java platform. Every
class in the Java system is a descendent, direct or indirect, of the Object class. This class
defines the basic state and behaviour that all objects must have, such as the ability to compare
oneself to another object, to convert to a string, to wait on a condition variable, to notify other
objects that a condition variable has changed, and to return the class of the object.
Your classes may want to override the following Object methods. The
equals/hashCode are listed together as they must be overridden together.
- clone
- equals/hashCode
- finalize
- toString
Your class cannot override these Object methods (they are final):
- getClass
- notify
- notifyAll
- wait
The clone method
You use the clone method to create an object from an existing object. To create a clone,
you write:
| Code: |
aCloneableObject.clone();
|
Object's implementation of this method checks to see if the object on which clone was
invoked implements the Cloneable interface, and throws a CloneNotSupportedException if
it does not. Note that Object itself does not implement Cloneable, so subclasses of Object
that don't explicitly implement the interface are not cloneable.
If the object on which clone was invoked does implement the Cloneable interface,
Object's implementation of the clone method creates an object of the same type as the
original object and initialises the new object's member variables to have the same values as
the original object's corresponding member variables.
The simplest way to make your class cloneable then, is to add implements Cloneable
to your class's declaration. For some classes the default behaviour of Object's clone method
works just fine. Other classes need to override clone to get correct behaviour.
Consider the Stack class, which contains a member variable that refers to a Vector.
If Stack relies on Object's implementation of clone, then the original stack and its clone
will refer to the same vector. Changing one stack will change the other, which is undesirable
behaviour.
Here then is an appropriate implementation of clone for our Stack class, which clones
the vector to ensure that the original stack and its clone do not refer to the same vector
(changes are indicated with a change in font):
| Code: |
public class Stack implements Cloneable {
private Vector items;
// code for Stack's methods and constructor not shown
protected Object clone() {
try {
Stack s = (Stack)super.clone(); // clone the stack
s.items = (Vector)items.clone(); // clone the vector
return s; // return the clone
} catch (CloneNotSupportedException e) {
// this shouldn't happen because Stack is Cloneable
throw new InternalError();
}
}
}
|
The implementation for Stack's clone method is relatively simple: It calls super.clone,
which Stack inherits from Object and which creates and initialises an instance of the correct
type. At this point, the original stack and its clone refer to the same vector. Next the method
clones the vector.
Be careful: clone should never use new to create the clone and should not call
constructors. Instead, the method should call super.clone, which creates an object of the
correct type and allows the hierarchy of superclasses to perform the copying necessary to get
a proper clone.
The equals and hashCode methods
You must override the equals and hashCode methods together.
The equals method compares two objects for equality and returns true if they are
equal. The equals method provided in the Object class uses the identity function to
determine if objects are equal (if the objects compared are the exact same object the method
returns true).
However, for some classes, two distinct objects of that type might be considered equal
if they contain the same information. Consider this code that tests two Integers, one and
anotherOne, for equality:
| Code: |
Integer one = new Integer(1);
anotherOne = new Integer(1);
if (one.equals(anotherOne))
System.out.println("objects are equal");
|
This program displays objects are equal even though one and anotherOne
reference two distinct objects. They are considered equal because the objects compared
contain the same integer value.
Your classes should only override the equals method if the identity function is not
appropriate for your class. If you override equals, then override hashCode as well.
The value returned by hashCode is an int that maps an object into a bucket in a hash
table. An object must always produce the same hash code. However, objects can share hash
codes (they aren't necessarily unique). Writing a "correct" hashing function is easy -- always
return the same hash code for the same object. Writing an "efficient" hashing function, one
that provides a sufficient distribution of objects over the buckets, is difficult.
Even so, the hashing function for some classes is relatively obvious. For example, an
obvious hash code for an Integer object is its integer value.
The finalize method
The Object class provides a method, finalize, that cleans up an object before it is
garbage collected. This method's role during garbage collection was discussed previously. The finalize method is called automatically by the system and most classes you write do
not need to override it. So you can generally ignore this method.
The toString method
Object's toString method returns a String representation of the object. You can use
toString along with System.out.println to display a text representation of an object, such
as the current thread:
| Code: |
System.out.println(Thread.currentThread().toString());
|
The String representation for an object depends entirely on the object. The String
representation of an Integer object is the integer value displayed as text. The String
representation of a Thread object contains various attributes about the thread, such as its
name and priority. For example, the previous line of code displays the following output:
Thread[main,5,main]
The toString method is very useful for debugging. It behooves you to override this
method in all your classes.
The getClass method
The getClass method is a final method that returns a runtime representation of the
class of an object. This method returns a Class object.
Once you have a Class object you can query it for various information about the class,
such as its name, its superclass, and the names of the interfaces that it implements. The
following method gets and displays the class name of an object:
| Code: |
void PrintClassName(Object obj) {
System.out.println("The Object's class is " +
obj.getClass().getName());
}
|
One handy use of a Class object is to create a new instance of a class without knowing
what the class is at compile time. The following sample method creates a new instance of the
same class as obj, which can be any class that inherits from Object (which means that it
could be any class):
| Code: |
Object createNewInstanceOf(Object obj) {
return obj.getClass().newInstance();
}
|
Part II continues here... Printer Friendly Page
Send to a Friend
..
Search here again if you need more info!
|