When you have a class C, and you want another class, D, that does everything that C does and more: use inheritance! Here's a simple example:
class RaceHorse extends Horse { // new fields private int myNumRacesWon; // new methods public int RacesWon() { return myNumRacesWon; } public void WinRace() { myNumRacesWon++; } }
In general, inheritance is used to implement an "is-a" relationship, not a "has-a" relationship. For example, a racehorse is a horse, as is a workhorse, so a reasonable inheritance hierarchy is:
Horse / \ RaceHorse WorkHorse(In this hierarchy, Horse is the base class; it is the superclass of both RaceHorse and WorkHorse, and both RaceHorse and WorkHorse are subclasses of Horse.) On the other hand, since a horse has a head, a body, and a tail, and a head has a nose and a mouth, the following would not be a good inheritance hierarchy:
Horse / | \ Head Body Tail / \ Nose MouthIt may make sense to define classes Horse, Head, Body, Tail, Nose, and Mouth, but the way they should be related is that the Head class has two fields of type Nose and Mouth, respectively, and the Horse class has three fields, of type Head, Body, and Tail.
The main benefit of inheritance is code reuse:
Note: You should probably not use inheritance if you don't
want most of the existing class's methods unchanged, or if functions
with parameters of that type will not still work correctly when
passed actual parameters of the new subclass type. Here's an example:
The SortedList class cannot reuse either the Lookup method or the
AddToEnd method.
(A new version of Lookup should be defined for the SortedList class
that is more efficient than the List version by takeing advantage of
the fact that the list is sorted.
As for AddToEnd, adding a new object to the end of a list does not
in general keep it sorted;
therefore, this method is simply not appropriate for a SortedList.)
However, this is a bigger problem for SortedList than it was for DList.
For DList, the AddToEnd method is still appropriate, it just
requires a modified implementation. For SortedList, AddToEnd is not
a legal operation, and an attempt to call that method would have to
result in an error. That also means that functions with List parameters
might not work for SortedLists.
Therefore, it does not seem reasonable to define the SortedList
class as a subclass of List.
Note that in general you should think about the whole class hierarchy
that you will need before defining any classes (i.e., design your base
class so that it can be extended to create the subclasses you want).
This is not always easy, but it should pay off in the long run.
Every field of the superclass is inherited.
For example:
Notes:
To address this problem, fields can be defined to be protected,
which means that subclass methods can access them, or the superclass
can provide public or protected functions to access and modify its
private fields. For example, if the Horse class includes:
Each superclass method (except its constructors) can be either
inherited: If no method with the same name is
(re)defined in the subclass, then the subclass has that method with
the same implementation as in the superclass.
overloaded: If the subclass defines a method with the
same name, but with a different number of arguments or different
argument types, then the subclass has two methods with that
name: the old one defined by the superclass, and the new one it
defined.
overridden: If the subclass defines a method with the
same name, and the same number and types of arguments, then the
subclass has only one method with that name: the new one it
defined.
Example:
A subclass's constructors always call a superclass constructor,
either explicitly or implicitly.
If there is no explicit call (and no call to another of the subclass
constructors), then the no-argument version of the superclass
constructor is called before executing any statements.
Example:
Explicit calls to a superclass constructor can be made using super:
And here is an example to show both a call to a superclass constructor, and
also how one subclass constructor can call another subclass constructor:
Notes:
Assume the following class has been defined:
Recall that every class extends Object. So you might wonder
which methods of Object you should consider overriding when you define
a new class. There are four methods that often should be overridden:
public String toString():
Returns a String representation
of the object. It is used, for example, by System.out.print to print an
object. The default version of toString is not very useful, so you should
override this method whenever you want to provide a String representation
of your class objects.
public boolean equals(Object ob):
Returns true iff the object (pointed to by "this") and ob are the
same. The default version uses pointer equality; i.e., it returns
true only if "this" and "ob" contain the same address. You may want
to override this method to provide a more liberal notion of equality.
For example, the String class overrides equals so that it returns true
for two Strings that contain the same sequence of characters.
public int hashCode():
Returns an integer for this object suitable for use as a hash code
(e.g., for use with the Hashtable class defined in javil.util). This
method should be overridden whenever the equals method is, so that
hashCode returns the same value for two "equal" objects.
protected Object clone():
Returns a copy of this object (note that no constructor is called for
the new object). The default version just copies the values of all
fields (i.e., a "shallow" copy). That is probably not what you want
when your class has fields that contain pointers (i.e., arrays or classes).
So in that case you should override the clone method to do a deep
copy -- clone all pointer fields.
To permit your object to be cloned you must
declare that your object implements the Cloneable interface.
(See the notes on INTERFACES.) For example:
Given the declaration above, List methods will be able to clone List objects;
however, users of the List class will not be able to call clone because it
is a protected method of the Object class.
Also, the (default) clone method will just copy the value of items, creating
a new List object whose items field points to the same array
(not good!). For example:
Summary of cloning: To permit your class objects to be cloned, you must:
Consider the following program:
Question 2(a):
Write a new version of the program in which main uses cloning to
copy from p1 to p2 (instead of assignment).
Be sure to include all appropriate changes to the Point class.
Question 2(b):
What is printed when your new version of the program is run?
Question 3:
Now suppose that the x,y coordinates of a Point are stored in an array,
rather than in two int fields; i.e., suppose we replace:
Consider a List class with the following operations:
void AddToEnd( Object ob ) // add ob to the end of this list
void Print() // print all objects in this list
boolean Lookup( Object ob ) // return true iff ob is on this list
Now consider two possible subclasses:
Assume that the List class is implemented using a linked list.
The DList class can reuse the List methods Print and Lookup.
For efficiency reasons it is probably desirable to implement the DList
class using a doubly linked list, so it will not be able to
reuse the AddToEnd method, because that method adds a node with just
two fields (the object and a pointer to the next list node), and
for a DList the method must add a node with three fields (the object,
a pointer to the next list node, and a pointer to the previous list node).
Nevertheless, because it can reuse both of the other methods (in addition
to defining a new method AddToFront),
and because functions with List parameters should work fine given DList
objects, it seems reasonable to define the DList class as a subclass
of List.
class Horse {
private int myAge;
private String myOwner;
private double myValue;
}
class RaceHorse extends Horse {
private int myNumRacesWon;
}
Every RaceHorse has four fields: 3 inherited fields and 1 new one.
private double myAge;
to the RaceHorse class, every RaceHorse will have five
fields, one of which (int myAge) is hidden.
However, it is a bad idea to hide fields because it can
lead to confusion and incorrect code.
public void WinRace() {
we get a compile-time error, because the myValue field is a private
field of the Horse class (so cannot be accessed in a RaceHorse method).
The same thing happens if the superclass field has package access and
the subclass is in a different package.
myNumRacesWon++;
myValue += 1000.0;
}protected double GetValue() {
then the RaceHorse class can implement the WinRace method like this:
return myValue;
}
protected void SetValue( double d ) {
myValue = d;
}public void WinRace() {
myNumRacesWon++;
SetValue( GetValue() + 1000.0);
}
class Horse {
...
public double GetValue() {
return myValue;
}
public void Birthday() {
// increment horse's age, recompute its value
}
public void Sell( String newOwner ) {
// change the myOwner field
}
}
class RaceHorse extends Horse {
...
public void Birthday( double d ) {
// increment horse's age, set value to given amount
}
public void Sell( String newOwner ) {
// change the myOwner field and set the myNumRacesWon field to 0
}
}
Now every RaceHorse has:
/
in | public Horse() { myValue = 0; } // no-arg constructor
superclass | public Horse( double d ) { myValue = d; } // 1-arg constructor
\
/
in | public RaceHorse() {myNumRacesWon = 0; } // no-arg constructor
subclass |
\
/
in main | RaceHorse r = new RaceHorse();
\
When the code "r = new RaceHorse()" is executed, the following happens:
// no-arg constructor
public RaceHorse() {
super( 2000.0 ); // call Horse's 1-arg constructor
myNumRacesWon = 0;
}
public RaceHorse( double d ) {
super( d ); // call Horse's 1-arg constructor
myNumRacesWon = 0;
}
public RaceHorse() {
this( 2000.0 ); // call to RaceHorse's 1-arg constructor
}
class Point {
protected int x, y;
// no-arg constructor
public Point() {
x = y = 0;
}
// constructor with 2 args
public Point(int a, int b) {
x = a;
y = b;
]
}
Define a class ColorPoint that:
public class List implements Cloneable {
If you forget to do this, an attempt to clone will cause the exception
CloneNotSupportedException to be thrown.
private Object items[]; // a pointer field!
}
...
List L1 = new List();
List L2 = L1.clone()
+--+ +--------------+
List L1: | -|--> | +--+ | +-----------+
+--+ | items: | -|--|----> | | | | |
| +--+ | +-> +-----------+
| ... | |
+--------------+ |
|
+--+ +--------------+ |
List L2: | -|--> | +--+ | | L2 points to a newly allocated
+--+ | items: | -|--|--+ List object, but its items
| +--+ | field points to the same
| ... | array as L1's items field!
+--------------+
To avoid these two problems, you should redefine the clone method for class
List as follows:
public class List implements Cloneable {
private Object items[];
public Object clone() {
try {
List tmp = (List)super.clone();
tmp.items = (Object[])items.clone();
return tmp;
} catch (CloneNotSupportedException ex) {
// cannot get here: List implements Cloneable, so do arrays
throw new InternalError(ex.toString());
}
}
}
Notes on this example code:
class Point {
private int x, y;
public String toString() {
return "(" + x + ", " + y + ")";
}
public void SetPoint(int a, int b) {
x = a;
y = b;
}
public static void main(String[] args) {
Point p1 = new Point();
p1.SetPoint(10, 20);
Point p2 = p1;
p1.SetPoint(20, 30);
System.out.println(p2);
}
}
Question 1:
What is printed when this program is run?
private x, y;
with:
private int [] coords = new int[2];
and that we change the SetPoint method to:
public void SetPoint(int a, int b) {
coords[0] = a;
coords[1] = b;
}
and that we change the toString method appropriately.
Write a new version of the clone method so that there is no aliasing when
a Point is cloned.
Solutions to Self-Study Questions