3.5. Subclasses and Inheritance
The Circle defined earlier is a simple class
that distinguishes circle objects only by their radii. Suppose,
instead, that we want to represent circles that have both a size and
a position. For example, a circle of radius 1.0 centered at point 0,0
in the Cartesian plane is different from the circle of radius 1.0
centered at point 1,2. To do this, we need a new class, which
we'll call PlaneCircle.
We'd like to add the ability to represent the
position of a circle without losing any of the existing functionality
of the Circle class. This is done by defining
PlaneCircle as a subclass of
Circle so that PlaneCircle
inherits the fields and methods of its superclass,
Circle. The ability to add functionality to a
class by subclassing, or extending, is central to the object-oriented
programming paradigm.
3.5.1. Extending a Class
Example 3-3 shows how we can implement
PlaneCircle as a subclass of the
Circle class.
Example 3-3. Extending the Circle class
public class PlaneCircle extends Circle {Note the use of the
// We automatically inherit the fields and methods of Circle,
// so we only have to put the new stuff here.
// New instance fields that store the center point of the circle
public double cx, cy;
// A new constructor method to initialize the new fields
// It uses a special syntax to invoke the Circle() constructor
public PlaneCircle(double r, double x, double y) {
super(r); // Invoke the constructor of the superclass, Circle()
this.cx = x; // Initialize the instance field cx
this.cy = y; // Initialize the instance field cy
}
// The area() and circumference() methods are inherited from Circle
// A new instance method that checks whether a point is inside the circle
// Note that it uses the inherited instance field r
public boolean isInside(double x, double y) {
double dx = x - cx, dy = y - cy; // Distance from center
double distance = Math.sqrt(dx*dx + dy*dy); // Pythagorean theorem
return (distance < r); // Returns true or false
}
}
keyword extends in the first line of Example 3-3. This keyword tells Java that
PlaneCircle extends, or subclasses,
Circle, meaning that it inherits the fields and
methods of that class.[4] The definition of the
isInside() method shows field inheritance; this
method uses the field r (defined by the
Circle class) as if it were defined right in
PlaneCircle itself. PlaneCircle
also inherits the methods of Circle. Thus, if we
have a PlaneCircle object referenced by variable
pc, we can say:
[4] C++ programmers should note
that extends is the Java equivalent of
: in C++; both are used to indicate the superclass
of a class.
double ratio = pc.circumference() / pc.area();This works just as if the area( ) and
circumference() methods were defined in
PlaneCircle itself.Another feature of subclassing is that
every PlaneCircle object is also a perfectly legal
Circle object. If pc refers to
a PlaneCircle object, we can assign it to a
Circle variable and forget all about its extra
positioning capabilities:
PlaneCircle pc = new PlaneCircle(1.0, 0.0, 0.0); // Unit circle at the originThis assignment of a PlaneCircle object to a
Circle c = pc; // Assigned to a Circle variable without casting
Circle variable can be done without a cast. As we
discussed in Section 2.9.6 in
Chapter 2 a widening conversion like this is always legal. The value
held in the Circle variable c
is still a valid PlaneCircle object, but the
compiler cannot know this for sure, so it doesn't
allow us to do the opposite (narrowing) conversion without a cast:
// Narrowing conversions require a cast (and a runtime check by the VM)
PlaneCircle pc2 = (PlaneCircle) c;
boolean origininside = ((PlaneCircle) c).isInside(0.0, 0.0);
3.5.1.1 Final classes
When
a class is declared with the final modifier, it
means that it cannot be extended or subclassed.
java.lang.String is an example of a
final class. Declaring a class
final prevents unwanted extensions to the class:
if you invoke a method on a String object, you
know that the method is the one defined by the
String class itself, even if the
String is passed to you from some unknown outside
source. Because String is final, no one can create
a subclass of it and change the meaning or behavior of its methods.Declaring a class final also allows the compiler
to make certain optimizations when invoking the methods of a class.
We'll explore this when we talk about method
overriding later in this chapter.
3.5.2. Superclasses, Object, and the Class Hierarchy
In our example,
PlaneCircle is a subclass from
Circle. We can also say that
Circle is the superclass of
PlaneCircle. The superclass of a class is
specified in its extends clause:
public class PlaneCircle extends Circle { ... }Every class you define has a superclass.
If you do not specify the superclass with an
extends clause, the superclass is the class
java.lang.Object. Object is a
special class for a couple of reasons:
- It is the only class in Java that does not have a superclass.
- All Java classes inherit the methods of Object.
Because
every class has a superclass, classes in Java form a class hierarchy,
which can be represented as a tree with Object at
its root. Figure 3-1 shows a partial class
hierarchy diagram that includes our Circle and
PlaneCircle classes, as well as some of the
standard classes from the Java API.
Figure 3-1. A class hierarchy diagram

3.5.3. Subclass Constructors
Look
again at the
PlaneCircle() constructor method of Example 3-3:
public PlaneCircle(double r, double x, double y) {This
super(r); // Invoke the constructor of the superclass, Circle()
this.cx = x; // Initialize the instance field cx
this.cy = y; // Initialize the instance field cy
}
constructor explicitly initializes the cx and
cy fields newly defined by
PlaneCircle, but it relies on the superclass
Circle( ) constructor to initialize the inherited
fields of the class. To invoke the superclass constructor, our
constructor calls
super(). super is a
reserved
word in Java. One of its uses is to invoke the constructor method of
a superclass from within the constructor method of a subclass. This
use is analogous to the use of this( ) to invoke
one constructor method of a class from within another constructor
method of the same class. Invoking a constructor using
super() is subject to the same restrictions as is
using this( ) :
- super( ) can be used in this way only within a
constructor method. - The call to the superclass constructor must appear as the first
statement within the constructor method, even before local variable
declarations.
The arguments passed to super( ) must match the
parameters of the superclass constructor. If the superclass defines
more than one constructor, super( ) can be used to
invoke any one of them, depending on the arguments passed.
3.5.4. Constructor Chaining and the Default Constructor
Java
guarantees that the constructor
method of a class is called whenever an instance of that class is
created. It also guarantees that the constructor is called whenever
an instance of any subclass is created. In order to guarantee this
second point, Java must ensure that every constructor method calls
its superclass constructor method. Thus, if the first statement in a
constructor does not explicitly invoke another constructor with
this() or super( ), Java
implicitly inserts the call super( ), that is, it
calls the superclass constructor with no arguments. If the superclass
does not have a constructor that takes no arguments, this implicit
invocation causes a compilation error.Consider what happens when we create a new instance of the
PlaneCircle class. First, the
PlaneCircle constructor is invoked. This
constructor explicitly calls super(r) to invoke a
Circle constructor, and that
Circle() constructor implicitly calls
super() to invoke the constructor of its
superclass, Object. The body of the
Object constructor runs first. When it returns,
the body of the Circle( ) constructor runs.
Finally, when the call to super(r) returns, the
remaining statements of the PlaneCircle( )
constructor are executed.What all this means is that constructor calls are chained; any time
an object is created, a sequence of constructor methods is invoked,
from subclass to superclass on up to Object at the
root of the class hierarchy. Because a superclass constructor is
always invoked as the first statement of its subclass constructor,
the body of the Object constructor always runs
first, followed by the constructor of its subclass and on down the
class hierarchy to the class that is being instantiated. There is an
important implication here; when a constructor is invoked, it can
count on the fields of its superclass to be initialized.
3.5.4.1 The default constructor
There is
one missing piece in the previous description of constructor
chaining. If a constructor does not invoke a superclass constructor,
Java does so implicitly. But what if a class is declared without a
constructor? In this case, Java implicitly adds a constructor to the
class. This default constructor does nothing but invoke the
superclass constructor. For example, if we don't
declare a constructor for the PlaneCircle class,
Java implicitly inserts this constructor:
public PlaneCircle() { super(); }If the superclass, Circle,
doesn't declare a no-argument constructor, the
super( ) call in this automatically inserted
default constructor for PlaneCircle( ) causes a
compilation error. In general, if a class does not define a
no-argument constructor, all its subclasses must define constructors
that explicitly invoke the superclass constructor with the necessary
arguments.If
a class does not declare any constructors, it is given a no-argument
constructor by default. Classes declared public
are given public constructors. All other classes
are given a default constructor that is declared without any
visibility modifier: such a constructor has default visibility. (The
notion of visibility is explained later in this chapter.) If you are
creating a public class that should not be
publicly instantiated, you should declare at least one
non-public constructor to prevent the insertion of
a default public constructor. Classes that should
never be instantiated (such as java.lang.Math or
java.lang.System) should define a
private
constructor. Such a constructor can never be invoked from outside of
the class, but it prevents the automatic insertion of the default
constructor.
3.5.4.2 Finalizer chaining?
You
might assume that since Java chains constructor methods, it also
automatically chains the finalizer methods for an object. In other
words, you might assume that the finalizer method of a class
automatically invokes the finalizer of its superclass, and so on. In
fact, Java does not do this. When you write a
finalize() method, you must explicitly invoke the
superclass finalizer. (You should do this even if you know that the
superclass does not have a finalizer because a future implementation
of the superclass might add a finalizer.)As we saw in our example finalizer earlier in the chapter, you can
invoke a superclass method with a special syntax that uses the
super keyword:
// Invoke the finalizer of our superclassWe'll discuss this syntax in more detail when we
super.finalize();
consider method overriding. In practice, the need for finalizer
methods, and thus finalizer chaining, rarely arises.
3.5.5. Hiding Superclass Fields
For the sake of example, imagine that our
PlaneCircle class needs to know the distance
between the center of the circle and the origin (0,0). We can add
another instance field to hold this value:
public double r;Adding the following line to the constructor computes the value of
the field:
this.r = Math.sqrt(cx*cx + cy*cy); // Pythagorean theoremBut wait; this new field r has the same name as
the radius field r in the
Circle superclass. When this happens, we say that
the field r of PlaneCircle
hides the field r of
Circle. (This is a contrived example, of course:
the new field should really be called
distanceFromOrigin. Although you should attempt to
avoid it, subclass fields do sometimes hide fields of their
superclass.)With this new definition of PlaneCircle, the
expressions r and this.r both
refer to the field of PlaneCircle. How, then, can
we refer to the field r of
Circle that holds the radius of the circle? A
special syntax for this uses the
super
keyword:
r // Refers to the PlaneCircle fieldAnother way to refer to a
this.r // Refers to the PlaneCircle field
super.r // Refers to the Circle field
hidden field is to cast this (or any instance of
the class) to the appropriate superclass and then access the field:
((Circle) this).r // Refers to field r of the Circle classThis casting technique is particularly useful when you need to refer
to a hidden field defined in a class that is not the immediate
superclass. Suppose, for example, that classes A,
B, and C all define a field
named x and that C is a
subclass of B, which is a subclass of
A. Then, in the methods of class
C, you can refer to these different fields as
follows:
x // Field x in class CYou cannot refer to a hidden field x in the
this.x // Field x in class C
super.x // Field x in class B
((B)this).x // Field x in class B
((A)this).x // Field x in class A
super.super.x // Illegal; does not refer to x in class A
superclass of a superclass with super.super.x.
This is not legal syntax.Similarly, if you have an instance c of class
C, you can refer to the three fields named
x like this:
c.x // Field x of class CSo far, we've been
((B)c).x // Field x of class B
((A)c).x // Field x of class A
discussing instance fields. Class fields can also be hidden. You can
use the same super syntax to refer to the hidden
value of the field, but this is never necessary since you can always
refer to a class field by prepending the name of the desired class.
Suppose that the implementer of PlaneCircle
decides that the Circle.PI field does not express
to enough decimal places. She can define her own class field
PI:
public static final double PI = 3.14159265358979323846;Now, code in PlaneCircle can use this more
accurate value with the expressions PI or
PlaneCircle.PI. It can also refer to the old, less
accurate value with the expressions super.PI and
Circle.PI. Note, however, that the area(
) and circumference() methods inherited
by PlaneCircle are defined in the
Circle class, so they use the value
Circle.PI, even though that value is hidden now by
PlaneCircle.PI.
3.5.6. Overriding Superclass Methods
When a class defines an instance method using
the same name, return type, and parameters as a method in its
superclass, that method overrides the method of
the superclass. When the method is invoked for an object of the
class, it is the new definition of the method that is called, not the
superclass's old definition. In Java 5.0 and later,
the return type of the overriding
method may be a subclass of return type of the overridden method
instead of being exactly the same type. This is known as a
covariant return and is described in Section 2.6.5 in Chapter 2.Method overriding is an important and useful technique in
object-oriented programming. PlaneCircle does not
override either of the methods defined by Circle,
but suppose we define another subclass of Circle,
named Ellipse.[5] In this case,
it is important for Ellipse to override the
area( ) and circumference()
methods of Circle since the formulas used to
compute the area and circumference of a circle do not work for
ellipses.
[5] Mathematical
purists may argue that since all circles are ellipses,
Ellipse should be the superclass and
Circle the subclass. A pragmatic engineer might
counter that circles can be represented with fewer instance fields,
so Circle objects should not be burdened by
inheriting unnecessary fields from Ellipse. In any
case, this is a useful example here.
The upcoming discussion of method
overriding considers only instance methods. Class methods behave
quite differently, and there isn't much to say. Like
fields, class methods can be hidden by a subclass but not overridden.
As noted earlier in this chapter, it is good programming style to
always prefix a class method invocation with the name of the class in
which it is defined. If you consider the class name part of the class
method name, the two methods have different names, so nothing is
actually hidden at all. It is, however, illegal for a class method to
hide an instance method.Before we go any further with the
discussion of method overriding, you should understand the difference
between method overriding and method overloading. As we discussed in
Chapter 2, method overloading refers to the
practice of defining multiple methods (in the same class) that have
the same name but different parameter lists. This is very different
from method overriding, so don't get them confused.
3.5.6.1 Overriding is not hiding
Although Java treats the fields and methods
of a class analogously in many ways, method overriding is not like
field hiding at all. You can refer to hidden fields simply by casting
an object to an instance of the appropriate superclass, but you
cannot invoke overridden instance methods with this technique. The
following code illustrates this crucial difference:
class A { // Define a class named AWhile this difference between method overriding and field hiding may
int i = 1; // An instance field
int f() { return i; } // An instance method
static char g() { return 'A'; } // A class method
}
class B extends A { // Define a subclass of A
int i = 2; // Hides field i in class A
int f() { return -i; } // Overrides instance method f in class A
static char g() { return 'B'; } // Hides class method g() in class A
}
public class OverrideTest {
public static void main(String args[]) {
B b = new B(); // Creates a new object of type B
System.out.println(b.i); // Refers to B.i; prints 2
System.out.println(b.f()); // Refers to B.f(); prints -2
System.out.println(b.g()); // Refers to B.g(); prints B
System.out.println(B.g()); // This is a better way to invoke B.g()
A a = (A) b; // Casts b to an instance of class A
System.out.println(a.i); // Now refers to A.i; prints 1
System.out.println(a.f()); // Still refers to B.f(); prints -2
System.out.println(a.g()); // Refers to A.g(); prints A
System.out.println(A.g()); // This is a better way to invoke A.g()
}
}
seem surprising at first, a little thought makes the purpose clear.
Suppose we are manipulating a bunch of Circle and
Ellipse objects. To keep track of the circles and
ellipses, we store them in an array of type
Circle[]. (We can do this because
Ellipse is a subclass of
Circle, so all Ellipse objects
are legal Circle objects.) When we loop through
the elements of this array, we don't have to know or
care whether the element is actually a Circle or
an Ellipse. What we do care about very much,
however, is that the correct value is computed when we invoke the
area() method of any element of the array. In
other words, we don't want to use the formula for
the area of a circle when the object is actually an ellipse! Seen in
this context, it is not surprising at all that method overriding is
handled differently by Java than is field hiding.
3.5.6.2 Dynamic method lookup
If we have a Circle[
] array that holds Circle and
Ellipse objects, how does the compiler know
whether to call the area( ) method of the
Circle class or the Ellipse
class for any given item in the array? In fact, the compiler does not
know this because it cannot know it. The compiler knows that it does
not know, however, and produces code that uses
dynamic method lookup at runtime. When
the interpreter runs the code, it looks up the appropriate
area( ) method to call for each of the objects in
the array. That is, when the interpreter interprets the expression
o.area(), it checks the actual type of the object
referred to by the variable o and then finds the
area( ) method that is appropriate for that type.
It does not simply use the area( ) method that is
statically associated with the type of the variable
o. This process of dynamic method lookup is
sometimes also called virtual method
invocation.[6]
[6] C++ programmers should note that dynamic
method lookup is what C++ does for virtual
functions. An important difference between Java and C++ is that Java
does not have a virtual keyword. In Java, methods
are virtual by default.
3.5.6.3 Final methods and static method lookup
Virtual method
invocation is fast, but method invocation is faster when no dynamic
lookup is necessary at runtime. Fortunately, Java does not always
need to use dynamic method lookup. In particular, if a method is
declared with the final modifier, it
means that the method definition is the final one; it cannot be
overridden by any subclasses. If a method cannot be overridden, the
compiler knows that there is only one version of the method, and
dynamic method lookup is not necessary.[7] In addition, all
methods of a final class are themselves implicitly
final and cannot be overridden. As we'll discuss
later in this chapter, private methods are not
inherited by subclasses and, therefore, cannot be overridden (i.e.,
all private methods are implicitly
final). Finally, class methods behave like fields
(i.e., they can be hidden by subclasses but not overridden). Taken
together, this means that all methods of a class that is declared
final, as well as all methods that are
final, private, or
static, are invoked without dynamic method lookup.
These methods are also candidates for inlining at runtime by a
just-in-time compiler ( JIT) or similar optimization tool.
[7] In this
sense, the final modifier is the opposite of the
virtual modifier in C++. All
non-final methods in Java are
virtual.
3.5.6.4 Invoking an overridden method
We've
seen the important differences between method overriding and field
hiding. Nevertheless, the Java syntax for invoking an overridden
method is quite similar to the syntax for accessing a hidden field:
both use the super keyword. The
following code illustrates:
class A {Recall that when you use
int i = 1; // An instance field hidden by subclass B
int f() { return i; } // An instance method overridden by subclass B
}
class B extends A {
int i; // This field hides i in A
int f() { // This method overrides f() in A
i = super.i + 1; // It can retrieve A.i like this
return super.f() + i; // It can invoke A.f() like this
}
}
super to refer to a hidden field, it is the same
as casting this to the superclass type and
accessing the field through that. Using super to
invoke an overridden method, however, is not the same as casting
this. In other words, in the previous code, the
expression super.f() is not the same as
((A)this).f( ).When the interpreter invokes an instance method with this
super syntax, a modified form of dynamic method
lookup is performed. The first step, as in regular dynamic method
lookup, is to determine the actual class of the object through which
the method is invoked. Normally, the dynamic search for an
appropriate method definition would begin with this class. When a
method is invoked with the super syntax, however,
the search begins at the superclass of the class. If the superclass
implements the method directly, that version of the method is
invoked. If the superclass inherits the method, the inherited version
of the method is invoked.Note that
the super keyword invokes the most immediately
overridden version of a method. Suppose class A
has a subclass B that has a subclass
C and that all three classes define the same
method f( ). The method C.f()
can invoke the method B.f( ), which it overrides
directly, with super.f( ). But there is no way for
C.f() to invoke A.f( )
directly: super.super.f( ) is not legal Java
syntax. Of course, if C.f() invokes B.f(
), it is reasonable to suppose that B.f(
) might also invoke A.f(). This kind of
chaining is relatively common when working with overridden methods:
it is a way of augmenting the behavior of a method without replacing
the method entirely. We saw this technique in the the example
finalize() method shown earlier in the chapter:
that method invoked super.finalize() to run its
superclass finalization method.Don't confuse the use
of super to invoke an overridden method with the
super() method call used in constructor methods to
invoke a superclass constructor. Although they both use the same
keyword, these are two entirely different syntaxes. In particular,
you can use super to invoke an overridden method
anywhere in the overriding class while you can use
super() only to invoke a superclass constructor as
the very first statement of a constructor.It is also important to remember that super can be
used only to invoke an overridden method from within the class that
overrides it. Given an Ellipse object
e, there is no way for a program that uses an
object (with or without the super syntax) to
invoke the area() method defined by the Circle class on this object.