Section 5.6
Interfaces, Nested Classes, and Other Details
THIS SECTION simply pulls together a few more miscellaneous features
of object oriented progrmming in Java. Read it now, or just look through it and
refer back to it later when you need this material.
Interfaces
Some object-oriented programming languages, such as C++, allow
a class to extend two or more superclasses. This is
called multiple inheritance.
In the illustration below, for example, class
E is shown as having both class A and class B as direct
superclasses, while class F has three direct superclasses.
Such multiple inheritance is not allowed
in Java. The designers of Java wanted to keep the language
reasonably simple, and felt that the benefits of
multiple inheritance were not worth the cost in
increased complexity. However, Java does have a feature
that can be used to accomplish many of the same goals
as multiple inheritance: interfaces.
We've encountered the term "interface" before, in
connection with black boxes in general and subroutines in particular.
The interface of a subroutine consists of the name of the subroutine,
its return type, and the number and types of its parameters. This
is the information you need to know if you want to call the subroutine.
A subroutine also has an implementation: the block of code which
defines it and which is executed when the subroutine is called.
In Java, interface is a reserved word with an additional, technical
meaning. An "interface" in this sense consists of a set of
subroutine interfaces, without any associated implementations.
A class can implement
an interface by providing an implementation for each of the
subroutines specified by the interface. Here is an example of a very simple
Java interface:
public interface Drawable {
public void draw(Graphics g);
}
This looks much like a class definition, except that the implementation
of the method draw() is omitted. A class that implements
the interface, Drawable, must provide an implementation
for this method. Of course, the class can also include other methods and variables.
For example,
class Line implements Drawable {
public void draw(Graphics g) {
. . . // do something -- presumably, draw a line
}
. . . // other methods and variables
}
Any class that implements the Drawable interface defines
a draw() instance method. Any object created from such a class
includes a draw() method. We say that an object
implements an interface if it belongs to a class that implements
the interface. For example, any object of type Line implements the Drawable
interface. Note that it is not enough for the object to include
a draw() method. The class that it belongs to has to say
that it "implements Drawable.
While a class can extend only one other class, it can implement
any number of interfaces. In fact, a class can both extend another
class and implement one or more interfaces. So, we can have
things like
class FilledCircle extends Circle
implements Drawable, Fillable {
. . .
}
The point of all this is that, although interfaces are not classes,
they are something very similar.
An interface is very much like an abstract class, that is,
a class that can never be used for constructing objects,
but can be used as a basis for making subclasses.
The subroutines in an interface are abstract methods,
which must be implemented in any concrete class that implements
the interface. And as with abstract classes, even though you
can't construct an object from an interface, you can declare a
variable whose type is given by the interface. For example,
if Drawable is an interface, and if Line
and FilledCircle are classes that implement Drawable,
then you could say:
Drawable figure; // Declare a variable of type Drawable. It can
// refer to any object that implements the
// Drawable interface.
figure = new Line(); // figure now refers to an object of class Line
figure.draw(g); // calls draw() method from class Line
figure = new FilledCircle(); // Now, figure refers to an object
// of class FilledCircle.
figure.draw(g); // calls draw() method from class FilledCircle
A variable of type Drawable can refer to any object
of any class that implements the Drawable interface.
A statement like figure.draw(g), above, is legal because
figure is of type Drawable, and any
Drawable object has a draw() method.
Note that a type is something
that can be used to declare variables. A type can also be used
to specify the type of a parameter in a subroutine, or the return
type of a function. In Java, a type can
be either a class, an interface, or one of the eight built-in
primitive types. These are the only possibilities.
Of these, however, only classes can be used to
construct new objects.
You are not likely to need to write your own interfaces until
you get to the point of writing fairly complex programs. However,
there are a few interfaces that are used in important ways in
Java's standard packages. You'll learn about some of these
standard interfaces in the next few chapters.
Nested Classes
A class seems like it should be a pretty important
thing. A class is a high-level building block of a program, representing
a potentially complex idea and its associated data and behaviors. I've
always felt a bit silly writing tiny little classes that exist only
to group a few scraps of data together. However, such trivial classes
are often useful and even essential. Fortunately, in Java, I can
ease the embarrassment, because one class can be nested inside another
class. My trivial little class doesn't have to stand on its own. It
becomes part of a larger more respectable class. This is particularly
useful when you want to create a little class specifically to
support the work of a larger class. And, more seriously, there are
other good reasons for nesting the definition of one class inside
another class.
In Java, a nested class or inner
class is any class whose definition is inside the definition
of another class. Inner classes can be either named
or anonymous. I will come back to the topic
of anonymous classes later in this section. A named inner class looks
just like any other class, except that it is nested inside another
class. (It can even contain further levels of nested classes, but
you shouldn't carry these things too far.)
Like any other item in a class, a named inner class can be either
static or non-static. A static nested class is part of the static
structure of the containing class. It can be used inside that class
to create objects in the usual way. If it has not been declared
private, then it can also be used outside the containing class,
but when it is used outside the class, its name must indicate its
membership in the containing class. This is similar to other
static components of a class: A static nested class is part of the
class itself in the same way that static member variables are
parts of the class itself.
For example, suppose
a class named WireFrameModel represents a set of lines
in three-dimensional space. (Such models are used to represent
three-dimensional objects in graphics programs.) Suppose that
the WireFrameModel class contains a static nested class,
Line, that represents a single line. Then, outside
of the class WireFrameModel, the Line class
would be referred to as WireFrameModel.Line. Of course,
this just follows the normal naming convention for static members of
a class. The definition of the WireFrameModel class
with its nested Line class would look, in outline, like
this:
public class WireFrameModel {
. . . // other members of the WireFrameModel class
static public class Line {
// Represents a line from the point (x1,y1,z1)
// to the point (x2,y2,z2) in 3-dimensional space.
double x1, y1, z1;
double x2, y2, z2;
} // end class Line
. . . // other members of the WireFrameModel class
} // end WireFrameModel
Inside the WireframeModel class, a Line object would
be created with the constructor "new Line()". Outside the
class, "new WireFrameModel.Line()" would be used.
A static nested class has full access to the members
of the containing class, even to the private members. This can be
another motivation for declaring a nested class, since it lets you
give one class access to the private members of another class without
making those members generally available to other classes.
When you compile the above class definition, two class files
will be created. Even though the definition of Line
is nested inside WireFrameModel, the compiled Line
class is stored in a separate file. The name of the class
file for Line will be WireFrameModel$Line.class.
Non-static nested classes are not, in practice, very different
from static nested classes, but a non-static nested class is
actually associated to an object rather than to the class in
which it is nested. This can get some getting used to.
Any non-static member of a class is not really part
of the class itself (although its source code is contained
in the class definition). This is true for non-static nested classes,
just as it is for any other non-static part of a class.
The non-static members of a class specify what will be contained
in objects that are created from that class.
The same is true -- at least logically -- for
non-static nested classes. It's as if each object
that belongs to the containing class has its own copy of the nested
class. This copy has access to all the instance methods and instance
variables of the object. Two copies of the nested class in
different objects differ because the instance variables and methods
they refer to are in different objects. In fact, the rule
for deciding whether a nested class should be static or
non-static is simple: If the class needs to use any instance
variable or instance method, make it non-static. Otherwise,
it might as well be static.
From outside the containing class, a non-static
nested class has to be referred to as variableName.NestedClassName,
where variableName is a variable that refers to the object that contains the
class. This is actually rather rare, however. A non-static nested class is
generally used only inside the class in which it is nested, and there it can be
referred to by its simple name.
In order to create an object that belongs to a non-static nested class,
you must first have an object that belongs to the containing class.
(When working inside the class, the object "this" is used implicitly.)
The nested class object is permanently associated with the containing
class object, and it has complete access to the members of the containing
class object. Looking at an example will help, and will hopefully
convince you that non-static nested classes are really very natural.
Consider a class that
represents poker games. This class might include a nested class to
represent the players of the game. This structure of the PokerGame
class could be:
class PokerGame { // Represents a game of poker.
class Player { // Represents one of the players in this game.
.
.
.
} // end class Player
private Deck deck; // A deck of cards for playing the game.
private int pot; // The amount of money that has been bet.
.
.
.
} // end class PokerGame
If game is a variable of type PokerGame, then, conceptually,
game contains its own copy of the Player class. In an
an instance method of a PokerGame object, a new Player object would be created
by saying "new Player()", just as for any other class.
(A Player object could be created outside the PokerGame
class with an expression such as "new game.Player()". Again,
however, this is rather rare.) The Player object will have access to the
deck and pot instance variables in the PokerGame
object. Each PokerGame object has its own
deck and pot and Players. Players of that poker game
use the deck and pot for that game; playes of another poker game use the
other game's deck and pot. That's the effect of making the Player
class non-static. This is the most natural way for players to behave.
A Player object represents a player of one particular poker game.
If Player were a static nested class, on the
other hand, it would represent the general idea of a poker player,
independent of a particular poker game.
In some cases, you might find yourself writing a nested class and
then using that class in just a single line of your program.
Is it worth creating such a class? Indeed, it can be,
but for cases like this you have the option of using
an anonymous nested class.
An anonymous class is created with a variation of the new operator
that has the form
new superclass-or-interface () {
methods-and-variables
}
This constructor defines a new class, without giving it a name, and it
simultaneously creates an object that belongs to that class.
This form of the new operator can be used in any statement where a regular
"new" could be used.
The intention of this expression is to create: "a new object belonging to a class that
is the same as superclass-or-interface
but with these methods-and-variables added."
The effect is to create a uniquely customized object, just at the point in the
program where you need it. Note that it is possible to base an anonymous class on an interface,
rather than a class. In this case, the anonymous class must implement
the interface by defining all the methods that are declared in the
interface.
Anonymous classes are most often used for handling events in graphical user
interfaces, and we will encounter them several times in the next two chapters.
For now, we will look at one not-very-plausible example. Consider the
Drawable interface, which is defined earlier in this section.
Suppose that we want a Drawable object that draws a filled, red,
100-pixel square. Rather than defining a separate class and then using that
class to create the object, we can use an anonymous class to create the
object in one statement:
Drawable redSquare = new Drawable() {
void draw(Graphics g) {
g.setColor(Color.red);
g.fillRect(10,10,100,100);
}
};
The semicolon at the end of this statement is not part of the class definition.
It's the semicolon that is required at the end of every declaration statement.
When a Java class is compiled, each anonymous nested class will produce a
separate class file. If the name of the main class is MainClass, for
example, then the names of the class files for the anonymous nested classes
will be MainClass$1.class, MainClass$2.class, MainClass$3.class,
and so on.
More about Access Modifiers
A class can be declared to be public. A public class can be
accessed from anywhere. Certain classes have to be public. A class that
defines a stand-alone application must be public, so that the system will
be able to get at its main() routine. A class that defines
an applet must be public so that it can be used by a Web browser. If
a class is not declared to be public, then it can only be used
by other classes in the same "package" as the class.
Packages are discussed in Section 4.5.
Classes that are not explicitly declared to be in any package
are put into something called the default package. All the examples
in this textbook are in the default package, so they are all
accessible to one another whether or not they are declared public.
So, except for applications and applets, which must be public,
it makes no practical difference whether our classes are declared
to be public or not.
However, once you start writing packages, it does make a difference.
A package should contain a set of related classes. Some of those classes
are meant to be public, for access from outside the package. Others
can be part of the internal workings of the package, and they should
not be made public. A package is a kind of black box. The public
classes in the package are the interface. (More exactly, the public
variables and subroutines in the public classes are the interface).
The non-public classes are part of the non-public implementation.
Of course, all the classes in the package have unrestricted access
to one another.
Following this model, I will tend to declare a class public
if it seems like it might have some general applicability.
If it is written just to play some sort of auxiliary role
in a larger project, I am more likely not to make it
public.
A member variable or subroutine in a class can also be declared to be
public, which means that it is accessible from anywhere.
It can be declared to be private, which means that it
accessible only from inside the class where it is defined.
Making a variable private gives you complete control over
that variable. The only code that will ever manipulate it
is the code you write in your class. This is an important
kind of protection.
If no access modifier is specified for a variable or subroutine,
then it is accessible from any class in the same package as the class.
As with classes, in this textbook there is no practical difference between
declaring a member public and using no access modifier at all.
However, there might be stylistic reasons for
preferring one over the other. And a real difference does arise once
you start writing your own packages.
There is a third access modifier that can be applied to a member
variable or subroutine. If it is declared to be protected,
then it can be used in the class where it is defined and in any
subclass of that class. This is obviously less restrictive than
private and more restrictive than public.
Classes that are written specifically to be used as a basis
for making subclasses often have protected members.
The protected members are there to provide a
foundation for the subclasses to build on. But they are still
invisible to the public at large.
Mixing Static and Non-static
Classes, as I've said, have two very distinct purposes. A class can be used
to group together a set of static member variables and static member
subroutines. Or it can be used as a factory for making objects.
The non-static variables and subroutine definintions in the class definition
specify the instance variables and methods of the objects. In most cases,
a class performs one or the other of these roles, not both.
Sometimes, however, static and non-static members are mixed in a single
class. In this case, the class plays a dual role. Sometimes, these
roles are completely separate. It is also possible for the static and
non-static parts of a class to interact. This happens when instance
methods use static member variables or call static member subroutines.
An instance method belongs to an object, not to the class itself, and there
can be many objects with their own versions of the instance method.
But there is only one copy of a static member variable. So, effectively,
we have many objects sharing that one variable.
Suppose, for example, that we want to write a PairOfDice
class that uses the Random class mentioned in
Section 3 for rolling the dice.
To do this, a PairOfDice object needs access to an object
of type Random. But there is no need for each PairOfDice
object to have a separate Random object. (In fact, it would
not even be a good idea: Because of the way random number generators
work, a program should, in general, use only one source of random
numbers.) A nice solution is to have a single Random variable
as a static member of the PairOfDice class, so that
it can be shared by all PairOfDice objects. For example:
class PairOfDice {
private static Random randGen = new Random();
// (Note: Assumes that java.util.Random has been imported.)
public int die1; // Number showing on the first die.
public int die2; // Number showing on the second die.
public PairOfDice() {
// Constructor. Creates a pair of dice that
// initially shows random values.
roll();
}
public void roll() {
// Roll the dice by setting each of the dice to be
// a random number between 1 and 6.
die1 = randGen.nextInt(6) + 1;
die2 = randGen.nextInt(6) + 1;
}
} // end class PairOfDice
As another example, let's rewrite the Student class that
was used in the Section 2. I've added
an ID for each student and a static member
called nextUniqueID. Although there is an
ID variable in each student object, there is
only one nextUniqueID variable.
public class Student {
private String name; // Student's name.
private int ID; // Unique ID number for this student.
public double test1, test2, test3; // Grades on three tests.
private static int nextUniqueID = 0;
// keep track of next available unique ID number
Student(String theName) {
// Constructor for Student objects;
// provides a name for the Student,
// and assigns the student a unique
// ID number.
name = theName;
nextUniqueID++;
ID = nextUniqueID;
}
public String getName() {
// Accessor method for reading value of private
// instance variable, name.
return name;
}
public int getID() {
// Accessor method for reading value of ID.
return ID;
}
public double getAverage() {
// Compute average test grade.
return (test1 + test2 + test3) / 3;
}
} // end of class Student
The initialization "nextUniqueID = 0" is done only once,
when the class is first loaded. Whenever a Student object is constructed
and the constructor says "nextUniqueID++;", it's always the
same static member variable that is being incremented. When the very first Student
object is created, nextUniqueID becomes 1. When the second object is
created, nextUniqueID becomes 2. After the third object, it becomes 3.
And so on. The constructor stores the new value of nextUniqueID in
the ID variable of the object that is being created. Of course,
ID is an instance variable, so every object has its own individual
ID variable. The class is constructed so that each student will
automatically get a different value for its ID variable. Furthermore,
the ID variable is private, so there is no way for this
variable to be tampered with after the object has been created.
You are guaranteed, just by the way the class is designed, that every
student object will have its own permanent, unique identification number.
Which is kind of cool if you think about it.
End of Chapter 5