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. 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. Before Java 8, anonymous classes were often used for handling events in GUI programs. With Java 8 and JavaFX, they can be mostly replaced in that context by lambda expressions.
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 stream API and the Runnable interface, which are discussed in Section 4.5. This subroutine will print out the numbers 1 to 10 in some indeterminate order (since it uses a parallel stream):
static void print1to10() { ArrayList<Runnable> printers = new ArrayList<>(); for (int i = 1; i <= 10; i++) { int x = i; printers.add( () -> System.out.println(x) ); } printers.parallelStream().forEach( r -> r.run() ); }
The local variable x 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.