Nested classes are classes that are declared as members of other classes or scopes. Nesting classes is one way to better organize your code. For example, say you have a non-nested class (also known as a top-level class) that stores objects in a resizable array, followed by an iterator class that returns each object. Rather than pollute the top-level class’s namespace, you could declare the iterator class as a member of the resizable array collection class. This works because the two are closely related.
In Java, nested classes are categorized as either static member classes or inner classes. Inner classes are non-static member classes, local classes, or anonymous classes. In this tutorial you’ll learn how to work with static member classes and the three types of inner classes in your Java programs.
What you’ll learn in this Java tutorial
- About static classes in Java
- About the three types of inner classes:
- Non-static member classes
- Local classes
- Anonymous classes
- Examples of using nested classes in your Java programs
Static classes in Java
In Classes and objects in Java, you learned how to declare static fields and static methods as members of a class, and in Class and object initialization in Java, you learned how to declare static initializers as members of a class. Now you’ll learn how to declare static classes. Formally known as static member classes, these are nested classes that you declare at the same level as these other static entities, using the static
keyword. Here’s an example of a static member class declaration:
class C
{
static int f;
static void m() {}
static
{
f = 2;
}
static class D
{
// members
}
}
This example introduces top-level class C
with static field f
, static method m()
, a static initializer, and static member class D
. Notice that D
is a member of C
. The static field f
, static method m()
, and the static initializer are also members of C
. Since all these elements belong to class C
, it is known as the enclosing class. Class D
is known as the enclosed class.
Enclosure and access rules
Although it is enclosed, a static member class cannot access the enclosing class’s instance fields and invoke its instance methods. However, it can access the enclosing class’s static fields and invoke its static methods, even those members that are declared private
. To demonstrate, Listing 1 declares an EnclosingClass
with a nested SMClass
.
Listing 1. Declaring a static member class (EnclosingClass.java, version 1)
class EnclosingClass
{
private static String s;
private static void m1()
{
System.out.println(s);
}
static void m2()
{
SMClass.accessEnclosingClass();
}
static class SMClass
{
static void accessEnclosingClass()
{
s = "Called from SMClass's accessEnclosingClass() method";
m1();
}
void accessEnclosingClass2()
{
m2();
}
}
}
Listing 1 declares a top-level class named EnclosingClass
with class field s
, class methods m1()
and m2()
, and static member class SMClass
. SMClass
declares class method accessEnclosingClass()
and instance method accessEnclosingClass2()
. Note the following:
m2()
‘s invocation ofSMClass
‘saccessEnclosingClass()
method requires theSMClass.
prefix becauseaccessEnclosingClass()
is declaredstatic
.accessEnclosingClass()
is able to accessEnclosingClass
‘ss
field and call itsm1()
method, even though both have been declaredprivate
.
Listing 2 presents the source code to an SMCDemo
application class that demonstrates how to invoke SMClass
‘s accessEnclosingClass()
method. It also demonstrates how to instantiate SMClass
and invoke its accessEnclosingClass2()
instance method.
Listing 2. Invoking a static member class’s methods (SMCDemo.java)
public class SMCDemo
{
public static void main(String[] args)
{
EnclosingClass.SMClass.accessEnclosingClass();
EnclosingClass.SMClass smc = new EnclosingClass.SMClass();
smc.accessEnclosingClass2();
}
}
As shown in Listing 2, if you want to invoke a top-level class’s method from within an enclosed class, you must prefix the enclosed class’s name with the name of its enclosing class. Likewise, in order to instantiate an enclosed class you must prefix the name of that class with the name of its enclosing class. You can then invoke the instance method in the normal manner.
Compile Listings 1 and 2 as follows:
javac *.java
When you compile an enclosing class that contains a static member class, the compiler creates a class file for the static member class whose name consists of its enclosing class’s name, a dollar-sign character, and the static member class’s name. In this case, compiling results in EnclosingClass$SMCClass.class
and EnclosingClass.class
.
Run the application as follows:
java SMCDemo
You should observe the following output:
Called from SMClass's accessEnclosingClass() method
Called from SMClass's accessEnclosingClass() method
Example: Static classes and Java 2D
Java’s standard class library is a runtime library of class files, which store compiled classes and other reference types. The library includes numerous examples of static member classes, some of which are found in the Java 2D geometric shape classes located in the java.awt.geom
package. (You’ll learn about packages in the next Java 101 tutorial.)
The Ellipse2D
class found in java.awt.geom
describes an ellipse, which is defined by a framing rectangle in terms of an (x,y) upper-left corner along with width and height extents. The following code fragment shows that this class’s architecture is based on Float
and Double
static member classes, which both subclass Ellipse2D
:
public abstract class Ellipse2D extends RectangularShape
{
public static class Float extends Ellipse2D implements Serializable
{
public float x, y, width, height;
public Float()
{
}
public Float(float x, float y, float w, float h)
{
setFrame(x, y, w, h);
}
public double getX()
{
return (double) x;
}
// additional instance methods
}
public static class Double extends Ellipse2D implements Serializable
{
public double x, y, width, height;
public Double()
{
}
public Double(double x, double y, double w, double h)
{
setFrame(x, y, w, h);
}
public double getX()
{
return x;
}
// additional instance methods
}
public boolean contains(double x, double y)
{
// ...
}
// additional instance methods shared by Float, Double, and other
// Ellipse2D subclasses
}
The Float
and Double
classes extend Ellipse2D
, providing floating-point and double precision floating-point Ellipse2D
implementations. Developers use Float
to reduce memory consumption, particularly because you might need thousands or more of these objects to construct a single 2D scene. We use Double
when greater accuracy is required.
You cannot instantiate the abstract Ellipse2D
class, but you can instantiate either Float
or Double
. You also can extend Ellipse2D
to describe a custom shape that’s based on an ellipse.
As an example, let’s say you want to introduce a Circle2D
class, which isn’t present in the java.awt.geom
package. The following code fragment shows how you would create an Ellipse2D
object with a floating-point implementation:
Ellipse2D e2d = new Ellipse2D.Float(10.0f, 10.0f, 20.0f, 30.0f);
The next code fragment shows how you would create an Ellipse2D
object with a double-precision floating-point implementation:
Ellipse2D e2d = new Ellipse2D.Double(10.0, 10.0, 20.0, 30.0);
You can now invoke any of the methods declared in Float
or Double
by invoking the method on the returned Ellipse2D
reference (e.g., e2d.getX()
). In the same manner, you could invoke any of the methods that are common to Float
and Double
, and which are declared in Ellipse2D
. An example is:
e2d.contains(2.0, 3.0)
That completes the introduction to static member classes. Next we’ll look at inner classes, which are non-static member classes, local classes, or anonymous classes. You’ll learn how to work with all three inner class types.
Inner class type 1: Non-static member classes
You’ve learned previously in the Java 101 series how to declare non-static (instance) fields, methods, and constructors as members of a class. You can also declare non-static member classes, which are nested non-static classes that you declare at the same level as instance fields, methods, and constructors. Consider this example:
class C
{
int f;
void m() {}
C()
{
f = 2;
}
class D
{
// members
}
}
Here, we introduce top-level class C
with instance field f
, instance method m()
, a constructor, and non-static member class D
. All of these entities are members of class C
, which encloses them. However, unlike in the previous example, these instance entities are associated with instances of C
and not with the C
class itself.
Each instance of the non-static member class is implicitly associated with an instance of its enclosing class. The non-static member class’s instance methods can call the enclosing class’s instance methods and access its instance fields. To demonstrate this access, Listing 3 declares an EnclosingClass
with a nested NSMClass
.
Listing 3. Declare an enclosing class with a nested non-static member class (EnclosingClass.java, version 2)
class EnclosingClass
{
private String s;
private void m()
{
System.out.println(s);
}
class NSMClass
{
void accessEnclosingClass()
{
s = "Called from NSMClass's accessEnclosingClass() method";
m();
}
}
}
Listing 3 declares a top-level class named EnclosingClass
with instance field s
, instance method m()
, and non-static member class NSMClass
. Furthermore, NSMClass
declares instance method accessEnclosingClass()
.
Because accessEnclosingClass()
is non-static, NSMClass
must be instantiated before this method can be called. This instantiation must take place via an instance of EnclosingClass
, as shown in Listing 4.
Listing 4. NSMCDemo.java
public class NSMCDemo
{
public static void main(String[] args)
{
EnclosingClass ec = new EnclosingClass();
ec.new NSMClass().accessEnclosingClass();
}
}
Listing 4’s main()
method first instantiates EnclosingClass
and saves its reference in local variable ec
. The main()
method then uses the EnclosingClass
reference as a prefix to the new
operator, in order to instantiate NSMClass
. The NSMClass
reference is then used to call accessEnclosingClass()
.
Compile Listings 3 and 4 as follows:
javac *.java
When you compile an enclosing class that contains a non-static member class, the compiler creates a class file for the non-static member class whose name consists of its enclosing class’s name, a dollar-sign character, and the non-static member class’s name. In this case, compiling results in EnclosingClass$NSMCClass.class
and EnclosingClass.class
.
Run the application as follows:
java NSMCDemo
You should observe the following output:
Called from NSMClass's accessEnclosingClass() method
Example: Non-static member classes in HashMap
The standard class library includes non-static member classes as well as static member classes. For this example, we’ll look at the HashMap
class, which is part of the Java Collections Framework in the java.util
package. HashMap
, which describes a hash table-based implementation of a map, includes several non-static member classes.
For example, the KeySet
non-static member class describes a set-based view of the keys contained in the map. The following code fragment relates the enclosed KeySet
class to its HashMap
enclosing class:
public class HashMap extends AbstractMap
implements Map, Cloneable, Serializable
{
// various members
final class KeySet extends AbstractSet
{
// various members
}
// various members
}
The
and
syntaxes are examples of generics, a suite of related language features that help the compiler enforce type safety. I’ll introduce generics in an upcoming Java 101 tutorial. For now, you just need to know that these syntaxes help the compiler enforce the type of key objects that can be stored in the map and in the keyset, and the type of value objects that can be stored in the map.
HashMap
provides a keySet()
method that instantiates KeySet
when necessary and returns this instance or a cached instance. Here’s the complete method:
public Set keySet()
{
Set ks = keySet;
if (ks == null)
{
ks = new KeySet();
keySet = ks;
}
return ks;
}
Notice that the enclosed class’s (KeySet
‘s) constructor is called from within the enclosing class’s (HashSet
‘s) keySet()
instance method. This illustrates a common practice, especially because prefixing the new
operator with an enclosing class reference is rare.
Inner class type 2: Local classes
It’s occasionally helpful to declare a class in a block, such as a method body or sub-block. For example, you might declare a class that describes an iterator in a method that returns an instance of this class. Such classes are known as local classes because (as with local variables) they are local to the methods in which they are declared. Here is an example:
interface I
{
// members
}
class C
{
I m() // or even static I m()
{
class D implements I
{
// members
}
return new D();
}
}
Top-level class C
declares instance method m()
, which returns an instance of local class D
, which is declared in this method. Notice that m()
‘s return type is interface I
, which D
implements. The interface is necessary because giving m()
return type D
would result in a compiler error–D
isn’t accessible outside of m()
‘s body.
A local class can be associated with an instance of its enclosing class, but only when used in a non-static context. Also, a local class can be declared anywhere that a local variable can be declared, and has the same scope as a local variable. It can access the surrounding scope’s local variables and parameters, which must be declared final
. Consider Listing 5.
Listing 5. Declaring a local class within an enclosing class instance method (EnclosingClass.java, version 3)
class EnclosingClass
{
void m(final int x)
{
final int y = x * 3;
class LClass
{
int m = x;
int n = y;
}
LClass lc = new LClass();
System.out.println(lc.m);
System.out.println(lc.n);
}
}
Listing 5 declares EnclosingClass
with instance method m()
, declaring a local class named LClass
. LClass
declares a pair of instance fields (m
and n
). When LClass
is instantiated, the instance fields are initialized to the values of final parameter x
and final local variable y
, as shown in Listing 6.
Listing 6. A local class declares and initializes a pair of instance fields (LCDemo.java)
public class LCDemo
{
public static void main(String[] args)
{
EnclosingClass ec = new EnclosingClass();
ec.m(5);
}
}
Listing 6’s main()
method first instantiates EnclosingClass
. It then invokes m(5)
on this instance. The called m()
method multiplies this argument by 3
, instantiates LClass
, whose
method assigns the argument and the tripled value to its pair of instance fields and outputs LClass
‘s instance fields. (Note that in this case the local class uses the
method instead of a constructor to interact with its instance fields.)
Compile Listings 5 and 6 as follows:
javac *.java
When you compile a class whose method contains a local class, the compiler creates a class file for the local class whose name consists of its enclosing class’s name, a dollar-sign character, a 1-based integer, and the local class’s name. In this case, compiling results in EnclosingClass$1LClass.class
and EnclosingClass.class
.
A note about local class file name
When generating a name for a local class’s class file, the compiler adds an integer to the generated name. This integer is probably generated to distinguish a local class’s class file from a non-static member class’s class file. If two local classes have the same name, the compiler increments the integer to avoid conflicts. Consider the following example:
public class EnclosingClass
{
public void m1()
{
class LClass
{
}
}
public void m2()
{
class LClass
{
}
}
public void m3()
{
class LClass2
{
}
}
}
EnclosingClass
declares three instance methods that each declare a local class. The first two methods generate two different local classes with the same name. The compiler generates the following class files:
EnclosingClass$1LClass.class
EnclosingClass$1LClass2.class
EnclosingClass$2LClass.class
EnclosingClass.class
Run the application as follows:
java LCDemo
You should observe the following output:
5
15
Example: Using local classes in regular expressions
The standard class library includes examples of local class usage. For example, the Matcher
class, in java.util.regex
, provides a results()
method that returns a stream of match results. This method declares a MatchResultIterator
class for iterating over these results:
public Stream results()
{
class MatchResultIterator implements Iterator
{
// members
}
return StreamSupport.
stream(Spliterators.spliteratorUnknownSize(new MatchResultIterator(),
Spliterator.ORDERED |
Spliterator.NONNULL),
false);
}
Note the instantiation of MatchResultIterator()
following the class declaration. Don’t worry about parts of the code that you don’t understand; instead, think about the usefulness in being able to declare classes in the appropriate scopes (such as a method body) to better organize your code.
Inner class type 3: Anonymous classes
Static member classes, non-static member classes, and local classes have names. In contrast, anonymous classes are unnamed nested classes. You introduce them in the context of expressions that involve the new
operator and the name of either a base class or an interface that is implemented by the anonymous class:
// subclass the base class
abstract class Base
{
// members
}
class A
{
void m()
{
Base b = new Base()
{
// members
};
}
}
// implement the interface
interface I
{
// members
}
class B
{
void m()
{
I i = new I()
{
// members
};
}
}
The first example demonstrates an anonymous class extending a base class. Expression new Base()
is followed by a pair of brace characters that signify the anonymous class. The second example demonstrates an anonymous class implementing an interface. Expression new I()
is followed by a pair of brace characters that signify the anonymous class.
Anonymous classes are useful for expressing functionality that’s passed to a method as its argument. For example, consider a method for sorting an array of integers. You want to sort the array in ascending or descending order, based on comparisons between pairs of array elements. You might duplicate the sorting code, with one version using the less than () operator for one order, and the other version using the greater than (
>
) operator for the opposite order. Alternatively, as shown below, you could design the sorting code to invoke a comparison method, then pass an object containing this method as an argument to the sorting method.
Listing 7. Using an anonymous class to pass functionality as a method argument (Comparer.java)
public abstract class Comparer
{
public abstract int compare(int x, int y);
}
The compare()
method is invoked with two integer array elements and returns one of three values: a negative value if x
is less than y
, 0 if both values are the same, and a positive value if x
is greater than y
. Listing 8 presents an application whose sort()
method invokes compare()
to perform the comparisons.
Listing 8. Sorting an array of integers with the Bubble Sort algorithm (ACDemo.java)
public class ACDemo
{
public static void main(String[] args)
{
int[] a = { 10, 30, 5, 0, -2, 100, -9 };
dump(a);
sort(a, new Comparer()
{
public int compare(int x, int y)
{
return x - y;
}
});
dump(a);
int[] b = { 10, 30, 5, 0, -2, 100, -9 };
sort(b, new Comparer()
{
public int compare(int x, int y)
{
return y - x;
}
});
dump(b);
}
static void dump(int[] x)
{
for (int i = 0; i pass; i--)
if (c.compare(x[i], x[pass])
The main()
method reveals two calls to its companion sort()
method, which sorts an array of integers via the Bubble Sort algorithm. Each call receives an integer array as its first argument, and a reference to an object created from an anonymous Comparer
subclass as its second argument. The first call achieves an ascending order sort by subtracting y
from x
; the second call achieves a descending order sort by subtracting x
from y
.
Compile Listings 7 and 8 as follows:
javac *.java
When you compile a class whose method contains an anonymous class, the compiler creates a class file for the anonymous class whose name consists of its enclosing class’s name, a dollar-sign character, and an integer that uniquely identifies the anonymous class. In this case, compiling results in ACDemo$1.class
and ACDemo$2.class
in addition to ACDemo.class
.
Run the application as follows:
java ACDemo
You should observe the following output:
10 30 5 0 -2 100 -9
-9 -2 0 5 10 30 100
100 30 10 5 0 -2 -9
Example: Using anonymous classes with an AWT event handler
Anonymous classes can be used with many packages in the standard class library. For this example, we’ll use an anonymous class as an event handler in the Abstract Windowing Toolkit or Swing Windowing Toolkit. The following code fragment registers an event handler with Swing’s JButton
class, which is located in the javax.swing
package. JButton
describes a button that performs an action (in this case printing a message) when clicked.
JButton btnClose = new JButton("close");
btnClose.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
System.out.println("close button clicked");
}
});
The first line instantiates JButton
, passing close
as the button label to JButton
‘s constructor. The second line registers an action listener with the button. The action listener’s actionPerformed()
method is invoked whenever the button is clicked. The object passed to addActionListener()
is instantiated from an anonymous class that implements the java.awt.event.ActionListener
interface.
Conclusion
Java’s nesting capabilities help you organize non-top-level reference types. For top-level reference types, Java provides packages. The next Java 101 tutorial introduces you to packages and static imports in Java.