Contents:
Subclassing and Inheritance
Interfaces
Packages and Compilation Units
Variable and Method Visibility
Inside Arrays
Inner Classes
So far, we know how to create a Java class, and to create objects, which are instances of a class. But an object by itself isn't very interesting--no more interesting than, say, a table knife. You can marvel at a table knife's perfection, but you can't really do anything with it until you have some other pieces of cutlery and food to use the cutlery on. The same is true of objects and classes in Java: they're interesting by themselves, but what's really important comes from relationships that you establish between them.
That's what we'll cover in this chapter. In particular, we'll be looking at several kinds of relationships:
Inheritance relationships--how a class inherits methods and variables from its parent class.
Interfaces--how to declare that a class supports certain behavior and define a type to refer to that behavior.
Packaging--how to organize objects into logical groups.
Inner classes--a generalization of classes that lets you nest a class definition inside of another class.
Classes in Java exist in a class hierarchy. A class in Java can be declared
as a subclass of another class using the extends
keyword. A subclass inherits variables and
methods from its superclass and uses them as
if they're declared within the subclass itself:
class Animal {
float weight;
...
void eat() {
...
}
...
}
class Mammal extends Animal {
int heartRate;
// inherits weight
...
void breathe() {
...
}
// inherits eat()
} In the above example, an object of type Mammal has
both the instance variable weight and the method
eat(). They are inherited from
Animal.
A class can extend only one other class. To use the proper terminology, Java allows single inheritance of class implementation. Later we'll talk about interfaces, which take the place of multiple inheritance as it's primarily used in C++.
A subclass can, of course, be further subclassed. Normally, subclassing specializes or refines a class by adding variables and methods:
class Cat extends Mammal {
boolean longHair;
// inherits weight and heartRate
...
void purr() {
...
}
// inherits eat() and breathe()
} The Cat class is a type of
Mammal that is ultimately a type of
Animal. Cat objects inherit all
the characteristics of Mammal objects and, in turn,
Animal objects. Cat also
provides additional behavior in the form of the
purr() method and the longHair
variable. We can denote the class relationship in a diagram, as shown
in Figure 6.1.

A subclass inherits all members of its superclass not designated
as private. As we'll discuss shortly, other
levels of visibility affect what inherited members of the class can be
seen from outside of the class and its subclasses, but at a minimum, a
subclass always has the same set of visible members as its parent. For
this reason, the type of a subclass can be considered a subtype of its
parent, and instances of the subtype can be used
anywhere instances
of the supertype are allowed. Consider the following example:
Cat simon = new Cat(); Animal creature = simon;
The Cat simon in the
above example can be assigned to the Animal type
variable creature because Cat
is a subtype of Animal.
In the section on methods in Chapter 5, we saw that a local variable of the same name as an instance variable hides the instance variable. Similarly, an instance variable in a subclass can shadow an instance variable of the same name in its parent class, as shown in Figure 6.2.

In Figure 6.2, the variable
weight is declared in three places: as a local
variable in the method foodConsumption() of the
class Mammal, as an instance variable of the class
Mammal, and as an instance variable of the class
Animal. The actual variable selected depends on the
scope in which we are working.
In the above example, all variables were of the same type. About
the only reason for declaring a variable with the same type in a subclass
is to provide an alternate initializer. A more important use of shadowed
variables involves changing their types. We could, for example, shadow
an int variable with a double
variable in a subclass that needs decimal values instead
of integer values. We do this without changing the existing code because,
as its name suggests, when we shadow variables, we don't replace them
but instead mask them. Both variables still exist; methods of the
superclass see the original variable, and methods of the subclass see the
new version. The determination of what variables the various methods see
is static and happens at compile-time.
Here's a simple example:
class IntegerCalculator {
int sum;
...
}
class DecimalCalculator extends IntegerCalculator {
double sum;
...
} In this example, we override the instance variable
sum to change its type from int
to double.[1]
Methods
defined in the class IntegerCalculator see the
integer variable sum, while methods defined in
DecimalCalculator see the decimal variable
sum. However, both variables actually exist for a
given instance of DecimalCalculator, and they can
have independent values. In fact, any methods that
DecimalCalculator inherits from
IntegerCalculator actually see the integer variable
sum.
[1] Note that a better way to design our calculators would be to have an abstract
Calculatorclass with two subclasses:IntegerCalculatorandDecimalCalculator.
Since both variables exist in
DecimalCalculator, we need to reference
the variable inherited from IntegerCalculator. We
do that using the super reference:
int s = super.sum;
Inside of DecimalCalculator, the super
keyword used in this manner refers to the sum
variable defined in the superclass. I'll explain the use of super
more fully in a bit.
Another important point about shadowed variables has to do with how they
work when we refer to an object by way of a less derived type. For example,
we can refer to a DecimalCalculator object as
an IntegerCalculator. If we do so and then access
the variable sum, we get the integer variable,
not the decimal one:
DecimalCalculator dc = new DecimalCalculator(); IntegerCalculator ic = dc; int s = ic.sum; // Accesses IntegerCalculator sum
After this detailed explanation, you may still be wondering what shadowed variables are good for. Well, to be honest, the usefulness of shadowed variables is limited, but it's important to understand the concepts before we talk about doing the same thing with methods. We'll see a different and more dynamic type of behavior with method shadowing, or more correctly, method overriding.
In Chapter 5, we saw we could declare overloaded methods (i.e., methods with the same name but a different number or type of arguments) within a class. Overloaded method selection works the way we described on all methods available to a class, including inherited ones. This means that a subclass can define some overloaded methods that augment the overloaded methods provided by a superclass.
But a subclass does more than that; it can define a method that has exactly the same method signature (arguments and return type) as a method in its superclass. In that case, the method in the subclass overrides the method in the superclass and effectively replaces its implementation, as shown in Figure 6.3. Overriding methods to change the behavior of objects is another form of polymorphism (sub-type polymorphism): the one most people think of when they talk about the power of object-oriented languages.

In Figure 6.3, Mammal
overrides the reproduce() method of
Animal, perhaps to specialize the method for the
peculiar behavior of Mammals giving live
birth.[2]
The Cat object's
sleeping behavior is overridden to be different from that of a general
Animal, perhaps to accommodate cat naps. The
Cat class also adds the more unique behaviors of
purring and hunting mice.
[2] We'll ignore the platypus, which is an obscure nonovoviviparous mammal.
From what you've seen so far, overridden methods probably
look like they shadow methods in superclasses, just as variables
do. But overridden methods are actually more powerful than that. An
overridden method in Java acts like a virtual
method in C++. When there are multiple implementations of a method in
the inheritance hierarchy of an object, the one in the most derived
class always overrides the others, even if we refer to
the object by way of a less derived type. In other words, if we have a
Cat instance assigned to a variable of the more
general type Animal and we call its
sleep() method, we get the
sleep() method implemented in the
Cat class, not the one in
Animal:
Cat simon = new Cat(); Animal creature = simon; creature.sleep(); // Accesses Cat sleep();
In other respects, the variable creature looks
like an Animal. For example, access to a shadowed
variable would find the implementation in the
Animal class, not the Cat
class. However, because methods are virtual, the appropriate method in
the Cat class can be located, even though we are
dealing with an Animal object. This means we
can deal with specialized objects as if they were more general types
of objects and still take advantage of their specialized
implementations of behavior.
Much of what you'll be doing when you're writing a
Java applet or application is overriding methods defined by
various classes in the Java API. For example, think
back to the applets we developed in the tutorial in Chapter 2. Almost all of the methods we implemented for
those applets were overridden methods. Recall that we created a
subclass of Applet for each of the examples. Then
we overrode init() to set up our
applet and paint() to draw our
applet.
A common programming error in Java (at least for me) is to miss and accidentally overload a method when trying to override it. Any difference in the number or type of arguments or the return type of a method produces two overloaded methods instead of a single, overridden method. Make it a habit to look twice when overriding methods.
In a previous section, we mentioned that overloaded methods are selected by the compiler at compile-time. Overridden methods, on the other hand, are selected dynamically at run-time. Even if we create an instance of a subclass our code has never seen before (perhaps a new object type loaded from the network), any overriding methods that it contains will be located and invoked at run-time to replace those that existed when we last compiled our code.
In contrast, if we load a new class that implements an additional, more specific overloaded method, our code will continue to use the implementation it discovered at compile-time. Another effect of this is that casting (i.e., explicitly telling the compiler to treat an object as one of its assignable types) affects the selection of overloaded methods, but not overridden methods.
static methods do not belong to any object instance; they are accessed directly
through a class name, so they are not dynamically selected at run-time like
instance methods. That is why static methods are called "static"--they are
always bound at compile-time.
A static method in a superclass can be shadowed
by another static method in a subclass, as long as
the original method was not declared final.
However, you can't override a static method with
a nonstatic method. In other words, you can't
change a static method into an instance method
in a subclass.
When Java has to search dynamically for overridden methods in
subclasses, there's a small performance penalty. In languages like
C++, the default is for methods to act like shadowed variables, so you
have to declare explicitly the methods you want to be virtual. Java
is more dynamic, and by default, all instance methods
are virtual. In Java you can, however, go the other direction and
declare explicitly that an instance method can't be overridden, so
that it will not be subject to dynamic binding and will not suffer in
terms of performance.
This is done with the final modifier. We have seen
final used with variables to effectively make them
constants. When final is applied to a method, it
means that that method can't be overridden (its
implementation is constant). final can also be
applied to an entire class, which means the class can't be subclassed.
When javac, the Java compiler, is run with the
-O switch, it performs certain optimizations.
It can inline final methods to improve
performance (while slightly increasing the size of the resulting class file).
private methods, which are effectively
final, can also be inlined, and
final classes may also benefit from more
powerful optimizations.
Another kind of optimization allows you to include debugging code
in your Java source without penalty. Java doesn't have a preprocessor
to explicitly control what source is included, but you can get some of the
same effects by making a block of code conditional on a constant (i.e.,
static and final)
variable.
The Java compiler is smart enough to remove this code when it
determines that it won't be called. For example:
static final boolean DEBUG = false;
...
static void debug (String message) {
if (DEBUG) {
System.err.println(message);
// do other stuff
...
}
}
If we compile the above code using the -O switch, the compiler will recognize
that the condition on the DEBUG variable is always false, and the body of
the debug() method will be optimized away. But that's not all--since
debug() itself is also final it can be inlined, and an empty inlined method
generates no code at all. So when we compile with DEBUG set to false,
calls to the debug() method will generate no residual code at all.
By now you should have a good, intuitive idea as to how methods are selected from the pool of potentially overloaded and overridden method names of a class. If, however, you are dying for a dry definition, I'll provide one now. If you are satisfied with your understanding, you may wish to skip this little exercise in logic.
In a previous section, I offered an inductive rule for overloaded method resolution. It said that a method is considered more specific than another if its arguments are polymorphically assignable to the arguments of the second method. We can now expand this rule to include the resolution of overridden methods by adding the following condition: to be more specific than another method, the type of the class containing the method must also be assignable to the type of the class holding the second method.
What does that mean? Well, the only classes whose types are assignable are classes in the same inheritance hierarchy. So, what we're talking about now is the set of all methods of the same name in a class or any of its parent or child classes. Since subclass types are assignable to superclass types, but not vice versa, the resolution is pushed, in the way that we expect, down the chain, towards the subclasses. This effectively adds a second dimension to the search, in which resolution is pushed down the inheritance tree towards more refined classes and, simultaneously, towards the most specific overloaded method within a given class.
When we talked about exception handling in Chapter 4, we didn't mention
an important restriction that applies when you override a method.
When you override a method, the new method (the overriding method)
must adhere to the
throws clause of the method it overrides.
In other words, if an overridden method declares that it can throw an
exception, the overriding method must also specify that it can throw the
same kind of exception, or a subtype of that exception.
By allowing the exception to be a subtype of the one specified by the parent,
the overriding method can refine the type of exception thrown to go along
with its new behavior. For example:
// A more refined exception
class MeatInedibleException extends InedibleException { ...
}
class Animal {
void eat( Food f ) throws InedibleException { ...
}
class Herbivore extends Animal {
void eat( Food f ) throws InedibleException {
if ( f instanceof Meat )
throw new MeatInedibleException();
....
}
}
In this code, Animal specifies
that it can throw an InedibleException
from its eat() method.
Herbivore is a subclass of
Animal, so its
eat() method must
also be able to throw an
InedibleException. However,
Herbivore's
eat() method actually throws a more
specific exception: MeatInedibleException.
It can do this because
MeatInedibleException is a subtype of
InedibleException (remember that
exceptions are classes too). Our calling code's
catch clause can therefore be more specific:
Animal creature = ...
try {
creature.eat( food );
} catch ( MeatInedibleException ) {
// creature can't eat this food because it's meat
} catch ( InedibleException ) {
// creature can't eat this food
}However, if we don't care why the food is inedible, we're free to
catch InedibleException alone, because a
MeatInedibleException is also an
InedibleException.
The special references this and
super allow you to refer to the members of the
current object instance or those of the superclass, respectively. We
have seen this used elsewhere to pass a reference
to the current object and to refer to shadowed instance variables.
The reference super does the same for the parents
of a class. You can use it to refer to members of a superclass that
have been shadowed or overridden. A common arrangement is for an
overridden method in a subclass to do some preliminary work and then
defer to the method of the superclass to finish the job.
class Animal {
void eat( Food f ) throws InedibleException {
// consume food
}
}
class Herbivore extends Animal {
void eat( Food f ) throws MeatInedibleException {
// check if edible
...
super.eat( f );
}
}
In the above example, our Herbivore class
overrides the Animal
eat() method to first do some checking on
the food object. After doing its job it simply calls the (otherwise
overridden) implementation of eat() in its
superclass, using super.
super prompts a search for the method or
variable to begin in the scope of the immediate superclass rather than the
current class. The inherited method or variable found may reside in the
immediate superclass, or in a more distant one.
The usage of the super
reference when applied to overridden methods of a superclass is
special; it tells the method resolution system to stop the dynamic method
search at the superclass, instead of in the most derived class (as it
otherwise does). Without super, there would be no
way to access overridden methods.
As in C++, a cast explicitly tells the compiler
to change the apparent type of an object reference. Unlike in C++,
casts in Java are checked both at compile-time and at run-time to make
sure they are legal. Attempting to cast an object to an incompatible
type at run-time results in a
ClassCastException. Only casts between objects in
the same inheritance hierarchy (and as we'll see later, to
appropriate interfaces) are legal in Java and pass the scrutiny of the
compiler and the run-time system.
Casts in Java affect only the treatment of references; they never change the form of the actual object. This is an important rule to keep in mind. You never change the object pointed to by a reference by casting it; you change only the compiler's (or run-time system's) notion of it.
A cast can be used to narrow the type of a
reference--to make it more specific. Often, we'll do this when we have to
retrieve an object
from a more general type of collection or when it has been previously used
as a less derived type. (The prototypical example is using an object in
a Vector or Hashtable,
as you'll see in Chapter 9.) Continuing with our Cat
example:
Animal creature = ... Cat simon = ... creature = simon; // Okay // simon = creature; // Compile time error, incompatible type simon = (Cat)creature; // Okay
We can't reassign the reference in creature to the
variable simon even though we know it holds an
instance of a Cat (Simon).
We have to perform the indicated cast.
This is also called downcasting the reference.
Note that an implicit cast was performed when we went the other way to
widen the reference simon
to type Animal during the first assignment. In this
case, an explicit cast would have been legal, but superfluous.
If casting seems complicated, here's a simple way to
think about it. Basically, you can't lie about what an object is. If
you have a Cat object, you can cast it to a less
derived type (i.e., a type above it in the class hierarchy) such as
Animal or even Object, since all
Java classes are a subclass of Object. If you have
an Object you know is a
Cat, you can downcast the Object
to be an Animal or a
Cat. However, if you aren't sure if the
Object is a Cat or a
Dog at run-time, you should check it with
instanceof before you perform the cast. If you get
the cast wrong, Java throws a ClassCastException.
As I mentioned earlier, casting can affect the selection of
compile-time items like variables and overloaded methods, but not the
selection of overridden methods. Figure 6.4 shows
the difference. As shown in the top half of the diagram,
casting the reference simon to type
Animal (widening it) affects the selection of
the shadowed variable weight within it. However, as
the lower half of the diagram indicates, the cast doesn't affect the
selection of the overridden method sleep().

When we talked earlier about constructors, we discussed how the
special statement this() invokes an
overloaded constructor upon entry to another constructor. Similarly,
the statement super() explicitly
invokes the constructor of a superclass. Of course, we also talked
about how Java makes a chain of constructor calls that includes the
superclass's constructor, so why use
super() explicitly? When Java makes an implicit
call to the superclass constructor, it calls the default
constructor. So, if we
want to invoke a superclass constructor that
takes arguments, we have to do so explicitly using
super().
If we are going to call a superclass constructor with
super(), it must be the first statement of our
constructor, just as this() must be the first call
we make in an overloaded constructor. Here's a simple example:
class Person {
Person ( String name ) {
// setup based on name
...
}
...
}
class Doctor extends Person {
Doctor ( String name, String specialty ) {
super( name );
// setup based on specialty
...
}
...
} In this example, we use super() to take
advantage of the implementation of the superclass constructor and
avoid duplicating the code to set up the object based on its name. In
fact, because the class Person doesn't define a
default (no arguments) constructor, we have no choice but to call
super() explicitly. Otherwise, the compiler would
complain that it couldn't find an appropriate default constructor to
call. Said another way, if you subclass a class that has only
constructors that take arguments, you have to invoke one of the
superclass's constructors explicitly from your subclass
constructor.
Instance variables of the class are initialized upon return
from the superclass constructor, whether that's due to an explicit
call via super() or an implicit call to the default
superclass constructor.
We can now give the full story of how constructors are chained together and when instance variable initialization occurs. The rule has three parts and is applied repeatedly for each successive constructor invoked.
If the first statement of a constructor is an ordinary
statement--i.e., not a call to this() or
super()--Java inserts an implicit call to
super() to invoke the default constructor of the
superclass. Upon returning from that call, Java initializes the
instance variables of the current class and proceeds to execute
the statements of the current constructor.
If the first statement of a constructor is a call to a superclass
constructor via super(), Java invokes the selected
superclass constructor. Upon its return, Java initializes the current
class's instance variables and proceeds with the statements
of the current constructor.
If the first statement of a constructor is a call to an overloaded
constructor via this(), Java invokes the selected
constructor and upon its return simply proceeds with the statements of
the current constructor. The call to the superclass's constructor
has happened within the overloaded constructor, either explicitly or
implicitly, so the initialization of instance variables has already
occurred.
A method in Java can be declared with the
abstract modifier to indicate that it's just a
prototype. An abstract method has no body; it's simply a signature
definition followed by a semicolon. You can't directly use a class that
contains an abstract method; you must instead create a subclass that
implements the abstract method's body.
abstract void vaporMethod( String name );
In Java, a class that contains one or more abstract
methods must be explicitly declared as an abstract
class, also using the abstract modifier:
abstract class vaporClass {
...
abstract void vaporMethod( String name );
...
} An abstract class can contain other,
nonabstract methods and ordinary variable
declarations; however, it can't be instantiated. To be used, it must
be subclassed and its abstract methods must be
overridden with methods that implement a body. Not all
abstract methods have to be implemented in a single
subclass, but a subclass that doesn't override all its
superclass's abstract methods with actual,
concrete implementations must also be declared
abstract.
Abstract classes provide a framework for classes that are to be "filled in"
by the implementor.
The java.io.InputStream class, for example, has a
single abstract method called
read(). Various subclasses of
InputStream implement read() in
their own ways to read from their own sources. The rest of the
InputStream class, however, provides extended
functionality built on the simple read()
method. A subclass of InputStream inherits
these nonabstract methods that provide
functionality based on the simple read() method
that the subclass implements.
It's often desirable to specify the prototypes for a set of methods and provide no implementation. In Java, this is called an interface. An interface defines a set of methods a class must implement (i.e., the behavior of a class). A class in Java can simply say that it implements an interface and go about implementing those methods. A class that implements an interface doesn't have to inherit from any particular part of the inheritance hierarchy or use a particular implementation.