Chapter 14
Nested and Inner Classes
One of the reasons nested and inner classes sometimes seem overly complex is because the terms “nested classes” and “inner classes” are often used to mean different things in different texts. This book will stick to the definitions in The Java Language Specification, Third Edition, which is the formal specification from Sun Microsystems (http://java.sun.com/docs/books/jls/).
This chapter starts by defining what nested classes and inner classes are and continues by explaining types of nested classes.
An Overview of Nested Classes
Let's start this chapter by learning the correct definitions of nested and inner classes. A nested class is a class declared within the body of another class or interface. There are two types of nested classes: static inner classes and non-static nested classes. Non-static nested classes are called inner classes.
There are several types of inner classes:
images member inner classes
images local inner classes
images anonymous inner classes
The term “top level class” is used to refer to a class that is not defined within another class or interface. In other words, there is no class enclosing a top level class.
A nested class behaves pretty much like an ordinary (top level) class. A nested class can extend another class, implements interfaces, become the parent class of subclasses, etc. Here is an example of a simple nested class called Nested that is defined within a top level class named Outer.
public class Outer {
class Nested {
}
}
And, though uncommon, it is not impossible to have a nested class inside another nested class, such as this:
public class Outer {
class Nested {
class Nested2 {
}
}
}
To a top-level class, a nested class is just like other class members, such as methods and fields. For example, a nested class can have one of the four access modifiers: private, protected, default (package), and public. This is unlike a top level class that can only have either public or default.
Because nested classes are members of an enclosing class, the behavior of static nested classes and the behavior of inner classes are not exactly the same. Here are some differences.
images Static nested classes can have static members, inner classes cannot.
images Just like instance methods, inner classes can access static and non-static members of the outer class, including its private members. Static nested classes can only access the static members of the outer class.
images You can create an instance of a static nested class without first creating an instance of its outer class. By contrast, you must first create an instance of the outer class enclosing an inner class before instantiating the inner class itself.
These are the benefits of inner classes:
1. Inner classes can have access to all (including private) members of the enclosing classes.
2. Inner classes help you hide the implementation of a class completely.
2. Inner classes provides a shorter way of writing listeners in Swing and other event-based applications.
Now, let's review each type of static class.
Static Nested Classes
A static nested class can be created without creating an instance of the outer class. Listing 14.1 shows this.
Listing 14.1: A Static Nested Class
package app14;
class Outer1 {
private static int value = 9;
static class Nested1 {
int calculate() {
return value;
}
}
}
public class StaticNestedTest1 {
upblic static void main(String[] args) {
Outer1.Nested1 nested = new Outer1.Nested1();
System.out.println(nested.calculate());
}
}
There are a few things to note about static nested classes:
images You refer to a nested class by using this format:
OuterClassName.InnerClassName
images You do not need to create an instance of the enclosing class to instantiate a static nested class.
images You have access to the outer class static members from inside your static nested class
In addition, if you declare a member in a nested class that has the same name as a member in the enclosing class, the former will shadow the latter. However, you can always reference the member in the enclosing class by using this format.
OuterClassName.memberName
Note that this will still work although memberName is private. Examine the example in Listing 14.2.
Listing 14.2: Shadowing an outer class's member.
package app14;
class Outer2 {
private static int value = 9;
static class Nested2 {
int value = 10;
int calculate() {
return value;
}
int getOuterValue() {
return Outer2.value;
}
}
}
public class StaticNestedTest2 {
public static void main(String[] args) {
Outer2.Nested2 nested = new Outer2.Nested2();
System.out.println(nested.calculate()); // returns 10
System.out.println(nested.getOuterValue()); // returns 9
}
}
Member Inner Classes
A member inner class is a class whose definition is directly enclosed by another class or interface declaration. An instance of a member inner class can be created only if you have a reference to an instance of its outer class. To create an instance of an inner class from within the enclosing class, you call the inner class's constructor, just as you would other ordinary classes. However, to create an instance of an inner class from outside the enclosing class, you use the following syntax:
EnclosingClassName.InnerClassName inner =
enclosingClassObjectReference.new InnerClassName();
As usual, from within an inner class, you can use the keyword this to reference the current instance (the inner class's instance). To reference the enclosing class's instance you use this syntax.
EnclosingClassName.this
Listing 14.3 shows how you can create an instance of an inner class.
Listing 14.3: A member inner class
package app14;
class TopLevel {
private int value = 9;
class Inner {
int calculate() {
return value;
}
}
}
public class MemberInnerTest1 {
public static void main(String[] args) {
TopLevel topLevel = new TopLevel ();
TopLevel.Inner inner = topLevel.new Inner();
System.out.println(inner.calculate());
}
}
Notice how you created an instance of the inner class in Listing 14.3?
A member inner class can be used to hide an implementation completely, something you cannot do without employing an inner class. The following example shows how you can use a member class to hide an implementation completely.
Listing 14.4: Hiding implementations completely
package app14;
interface Printer {
void print(String message);
}
class PrinterImpl implements Printer {
public void print(String message) {
System.out.println(message);
}
}
class SecretPrinterImpl {
private class Inner implements Printer {
public void print(String message) {
System.out.println("Inner:" + message);
}
}
public Printer getPrinter() {
return new Inner();
}
}
public class MemberInnerTest2 {
public static void main(String[] args) {
Printer printer = new PrinterImpl();
printer.print("oh");
// downcast to PrinterImpl
PrinterImpl impl = (PrinterImpl) printer;
Printer hiddenPrinter =
(new SecretPrinterImpl()).getPrinter();
hiddenPrinter.print("oh");
// cannot downcast hiddenPrinter to Outer.Inner
// because Inner is private
}
}
The Printer interface in Listing 14.4 has two implementations. The first is the PrinterImpl class, which is a normal class. It implements the print method as a public method. The second implementation can be found in SecretPrinterImpl. However, rather than implementing the Printer interface, the SecretPrinterImpl defines a private class called Inner, which implements Printer. The getPrinter method of SecretPrinterImpl returns an instance of Inner.
What's the difference between PrinterImpl and SecretPrinterImpl? You can see this from the main method in the test class:
Printer printer = new PrinterImpl();
printer.print("Hiding implementation");
// downcast to PrinterImpl
PrinterImpl impl = (PrinterImpl) printer;
Printer hiddenPrinter = (new SecretPrinterImpl()).getPrinter();
hiddenPrinter.print("Hiding implementation");
// cannot downcast hiddenPrinter to Outer.Inner
// because Inner is private
You assign printer an instance of PrinterImpl, and you can downcast printer back to PrinterImpl. In the second instance, you assign Printer with an instance of Inner by calling the getPrinter method on SecretPrinterImpl. However, there is no way you can downcast hiddenPrinter back to SecretPrinterImpl.Inner because Inner is private and therefore not visible.
Local Inner Classes
A local inner class, or local class for short, is an inner class that by definition is not a member class of any other class (because its declaration is not directly within the declaration of the outer class). Local classes have a name, as opposed to anonymous classes that do not.
A local class can be declared inside any block of code, and its scope is within the block. For example, you can declare a local class within a method, an if block, a while block, and so on. You want to write a local class if an instance of the class is only used within the scope. For example, Listing 14.5 shows an example of a local class.
Listing 14.5: Local inner class
package app14;
import java.util.Date;
interface Logger {
public void log(String message);
}
public class LocalClassTest1 {
String appStartTime = (new Date()).toString();
public Logger getLogger() {
class LoggerImpl implements Logger {
public void log(String message) {
System.out.println(appStartTime + " : " + message);
}
}
return new LoggerImpl();
}
public static void main(String[] args) {
LocalClassTest1 test = new LocalClassTest1();
Logger logger = test.getLogger();
logger.log("Local class example");
}
}
The class in Listing 14.5 has a local class named LoggerImpl that resides inside a getLogger method. The getLogger method must return an implementation of the Logger interface and this implementation will not be used anywhere else. Therefore, it is a good idea to make an implementation that is local to getLogger. Note also that the log method within the local class has access to the instance field appStartTime of the outer class.
However, there is more. Not only does a local class have access to the members of its outer class, it also has access to the local variables. However, you can only access final local variables. The compiler will generate a compile error if you try to access a local variable that is not final.
Listing 14.6 modifies the code in Listing 14.5. The getLogger method in Listing 14.6 allows you to pass a String that will become the prefix of each line logged.
Listing 14.6: PrefixLogger test
package app14;
import java.util.Date;
interface PrefixLogger {
public void log(String message);
}
public class LocalClassTest2 {
public PrefixLogger getLogger(final String prefix) {
class LoggerImpl implements PrefixLogger {
public void log(String message) {
System.out.println(prefix + " : " + message);
}
}
return new LoggerImpl();
}
public static void main(String[] args) {
LocalClassTest2 test = new LocalClassTest2();
PrefixLogger logger = test.getLogger("DEBUG");
logger.log("Local class example");
}
}
Anonymous Inner Classes
An anonymous inner class does not have a name. A use of this type of nested class is for writing an interface implementation. For example, the AnonymousInnerClassTest class in Listing 14.7 creates an anonymous inner class which is an implementation of Printable.
Listing 14.7: Using an anonymous inner class
interface Printable {
void print(String message);
}
public class AnonymousInnerClassTest 1{
public static void main(String[] args) {
Printable printer = new Printable() {
public void print(String message) {
System.out.println(message);
}
}; // this is a semicolon
printer.print("Beach Music");
}
}
The interesting thing here is that you create an anonymous inner class by using the new keyword followed by what looks like a class's constructor (in this case Printable()). However, note that Printable is an interface and does not have a constructor. Printable() is followed by the implementation of the print method. Also, note that after the closing brace, you use a semicolon to terminate the statement that instantiates the anonymous inner class.
In addition, you can also create an anonymous inner class by extending an abstract or concrete class, as demonstrated in the code in Listing 14.8.
Listing 14.8: Using an anonymous inner class with an abstract class
abstract class Printable {
void print(String message) {
}
}
public class AnonymousInnerClassTest 1{
public static void main(String[] args) {
Printable printer = new Printable() {
public void print(String message) {
System.out.println(message);
}
}; // this is a semicolon
printer.print("Beach Music");
}
}
Note
Anonymous classes are often used in Swing applications. See Chapter 16, “Swinging Higher” for details.
Behind Nested and Inner Classes
The JVM does not know the notion of nested classes. It is the compiler that works hard to compile an inner class into a top level class incorporating the outer class name and the inner class name as the name, both separated by a dollar sign. That is, the code that employs an inner class called Inner that resides inside Outer like this
public class Outer {
class Inner {
}
}
will be compiled into two classes: Outer.class and Outer$Inner.class.
What about anonymous inner classes? For anonymous classes, the compiler takes the liberty of generating a name for them, using numbers. Therefore, you'll see something like Outer$1.class, Outer$2.class, etc.
When a nested class is instantiated, the instance lives as a separate object in the heap. It does not actually live inside the outer class object.
However, with inner class objects, they have an automatic reference to the outer class object as shown. This reference does not exist in an instance of a static nested class, because a static nested class does not have access to its outer class's instance members.
How does an inner class object obtain a reference to its outer class object? Again, this happens because the compiler changes the constructor of the inner class a bit when the inner class is compiled, namely it adds an argument to every constructor. This argument is of type the outer class.
For example, a constructor like this:
public Inner()
is changed to this.
public Inner(Outer outer)
And, this
public Inner(int value)
to
public Inner(Outer outer, int value)
Note
Remember that the compiler has the discretion to change the code it compiles. For example, if a class (top level or nested) does not have a constructor, it adds a no-arg constructor to it.
The code that instantiates an inner class is also modified, with the compiler passing a reference to the outer class object to the inner class constructor. If you write:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
the compiler will change it to
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(outer);
When an inner class is instantiated inside the outer class, of course, the compiler passes the current instance of the outer class object using the keyword this.
// inside the Outer class
Inner inner = new Inner();
becomes
// inside the Outer class
Inner inner = new Inner(this);
Now, here is another piece of the puzzle. How does a nested class access its outer class's private members? No object is allowed to access another object's private members. Again, the compiler changes your code, creating a method that accesses the private member in the outer class definition. Therefore,
class TopLevel {
private int value = 9;
class Inner {
int calculate() {
return value;
}
}
}
is changed to two classes like this:
class TopLevel {
private int value = 9;
TopLevel() {
}
// added by the compiler
static int access$0(TopLevel toplevel) {
return toplevel.value;
}
}
class TopLevel$Inner {
final TopLevel this$0;
TopLevel$Inner(TopLevel toplevel) {
super();
this$0 = toplevel;
}
int calculate() {
// modified by the compiler
return TopLevel.access$0(this$0);
}
}
The addition happens in the background so you will not see it in your source. The compiler adds the access$0 method that returns the private member value so that the inner class can access the private member.
Summary
A nested class is a class whose declaration is within another class. There are four types of nested classes:
images Static nested classes
images Member inner classes
images Local inner classes
images Anonymous inner classes
The benefits of using nested classes include hiding the implementation of a class completely and as a shorter way of writing a class whose instance will only live within a certain context.
Questions
1. What is a nested class and what is an inner class?
2. What can you use nested classes for?
3. What is an anonymous class?
0 comments:
Post a Comment