9 Javanotes 9.0, Section 5.8 — Nested Classes
Section 5.8
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 is any class whose definition is inside the
definition of another class. (In fact, a class can even be nested inside a method,
which must, of course, itself be inside a class.)
Nested classes can be either named
or anonymous. I will come
back to the topic of anonymous classes later in this section. A named nested
class, like most other things that occur in classes, can be either static or
non-static. Interfaces, like classes, can be nested inside class definitions and
can be either static or non-static. (In fact, interface definitions can contain
static nested classes and interfaces, but that is not something that you
will see in this textbook.)
5.8.1 Static Nested Classes
The definition of a static nested class looks just like the definition of any other class,
except that it is nested inside another class and it has the modifier static
as part of its declaration. 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 is used
outside the containing class, its name
must indicate its membership in the containing class. That is, the full name of
the static nested class consists of the name of the class in which it is nested, followed
by a period, followed by the name of the nested 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. 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
The full name of the nested class is WireFrameModel.Line.
That name can be used, for example, to declare variables.
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 static members of the containing
class, even to the private members. Similarly, the containing class has full
access to the members of the nested class, even if they are marked private.
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. Note also that a nested class can itself be private,
meaning that it can only be used inside the class in which it is nested.
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.
5.8.2 Inner Classes
Non-static nested classes are referred to as inner classes.
Inner classes are not, in practice, very different from static
nested classes, but a non-static nested class is actually associated with an
object rather than with the class in which its definition is nested. This can take 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 inner 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 inner classes. It’s as if each object that belongs
to the containing class has its own copy of the nested class (although it does not
literally contain a copy of the compiled code for the nested class). This copy
has access to all the instance methods and instance variables of the object,
even to those that are declared private.
The two copies of the inner class in two 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 nested class needs to use any instance variable or instance method
from the containing class, make the nested class
non-static. Otherwise, it might as well be static.
In most cases, an inner class is used only within the class where it is
defined. When that is true, using the inner class is really not much different from
using any other class. You can create variables and declare objects using the
simple name of the inner class in the usual way (although you can only do that
in the non-static part of the class).
From outside the containing class, however, an inner class has to be
referred to using a name of the form variableName.NestedClassName,
where variableName is a variable that refers to the object that
contains the inner class. In order to create an object that belongs to an inner class, you
must first have an object that belongs to the containing class. (When working
inside the class, the object “this” is used implicitly.)
Looking at
an example will help, and will hopefully convince you that inner
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.
The structure of the PokerGame class could be:
public 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 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
“game.new Player()“. Again, however, this is 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; players of another poker game use
the other game’s deck and pot. That’s the effect of making the Player
class non-static: it associates any Player object with some particular
PokerGame object and gives it access to the instance variables for
that particular game. This is the most natural way for players to behave. A
Player object represents a player of one particular poker game. If
Player were an independent class or a static nested class,
on the other hand, it would represent the general idea of a poker player, independent of a
particular poker game.
5.8.3 Anonymous Inner Classes
In some cases, you might find yourself writing an inner 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 inner class. An anonymous class is
created with a variation of the new operator that has the form
new superclass-or-interface ( parameter-list ) {
methods-and-variables
}
This constructor defines a new class, without giving it a name. At run time, it
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. If an interface
is used as a base, the parameter-list must be empty. Otherwise, it can contain
parameters for a constructor in the superclass.
For now, we will look at one not-very-plausible example. Suppose that Drawable
is an interface defined as:
public interface Drawable {
public void draw(GraphicsContext g);
}
Suppose
that we want a Drawable object that draws a filled, red, 100-pixel
square. Rather than defining a new, 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() {
public void draw(GraphicsContext g) {
g.setFill(Color.RED);
g.fillRect(10,10,100,100);
}
};
Then redSquare refers to an object that implements Drawable
and that draws a red square when its draw() method is called.
By the way, the semicolon at the end of the statement is not part of the class
definition; it’s the semicolon that is required at the end of every declaration
statement.
Anonymous classes are often used for actual parameters. For example, consider the
following simple method, which draws a Drawable in two different graphics contexts:
void drawTwice( GraphicsContext g1, GraphicsContext g2, Drawable figure ) {
figure.draw(g1);
figure.draw(g2);
}
When calling this method, the third parameter can be created using an anonymous inner class.
For example:
drawTwice( firstG, secondG, new Drawable() {
void draw(GraphicsContext g) {
g.fillOval(10,10,100,100);
}
} );
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.
Of course, in this example, Drawable is a functional interface,
and we could use lambda expressions (Section 4.5) instead of anonymous classes.
The last example could then be written simply
drawTwice( firstG, secondG, g -> g.fillOval(10,10,100,100) );
and redSquare could be defined as
Drawable redSquare = g -> {
g.setFill(Color.RED);
g.fillRect(10,10,100,100);
};
This approach has the advantage that it does not create an extra .class file.
However, lambda expressions can only be used with functional interfaces, while anonymous
classes can be used with any interface or class.
5.8.4 Local Classes and Lambda Expressions
A class can be defined inside a subroutine definition. Such classes are
called local classes. A local class can only be
used inside the subroutine where it is defined. However, an object
that is defined by a local class can be used outside that subroutine.
It can be returned as the value of the subroutine, or it can be passed
as a parameter to another subroutine. This is possible because an
object belonging to some class B can be
assigned to a variable of type A, as long
as B is a subclass of A or,
if A is an interface, as long as
class B implements interface A.
For example, if a subroutine takes a parameter of type
Drawable, where Drawable
is the interface defined above, then any object that implements
Drawable can be passed as a parameter to that
subroutine. And that object can be defined by a local class.
In an example earlier in this section, we passed a customized object
of type Drawable to the drawTwice()
method, which takes a parameter of type Drawable.
In that example, the class was an anonymous inner class. Local classes
are often anonymous, but that is not required. It is also true that
anonymous classes are often local classes, but that is also not required. For
example, an anonymous class could be used to define the initial value
of a global variable. In that case, the anonymous class is not enclosed
in any subroutine and therefore is not local.
The definition of a local class can use local variables from the
subroutine where it is defined. It can also use parameters to
that subroutine. However, there is a restriction on the use of such
variables and parameters in a local class: The local variable or parameter must be
declared to be final or, if it is not explicitly declared final,
then it must be “effectively final.” A parameter is effectively final
if its value is not changed inside the subroutine (including in any
local class that references the parameter). A local variable is
effectively final if its value is never changed after it is initialized.
Note that there is no such restriction on global variables that
are used in the definition of a local class.
The same restriction on the use of local variables also applies to
lambda expressions, which are very similar to anonymous classes. Here
is an example using the FunctionR2R
functional interface from Subsection 4.5.2, which defines the
single method “double valueAt(double x))“.
This code segment creates an array of FunctionR2R
objects, where multpliers[i] is a function that multiplies
its parameter by i:
FunctionR2R[] multipliers = new FunctionR2R[100];
for (int i = 0; i < 100; i++) {
int n = i;
multipliers[i] = z -> n * z;
}
The local variable n is effectively final and therefore
can be used in the lambda expression. On the other hand, it would have
been illegal to use the variable i directly in the lambda
expression, since i is not effectively final; its value
is changed when i++ is executed. Note also that this example
could be written using an anonymous class instead of a lambda expression:
FunctionR2R[] multipliers = new FunctionR2R[100];
for (int i = 0; i < 100; i++) {
int n = i;
multipliers[i] = new FunctionR2R() {
public double valueAt(double x) {
return n * x;
}
};
}