The Ultimate Guide to Nested Classes in Java
Nested classes are simply classes defined inside other classes. Nested classes can be used to increase encapsulation and make your code more readable and maintainable. If you have a class that is only being used in one place, then making it a nested class may be a good idea.
In this tutorial, we'll explore nested classes in depth. We'll explain what a nested class is and why it can be useful. We'll look at the different types of nested classes including static nested classes, inner classes, local classes and anonymous classes. We'll explain what makes these classes different and provide examples of each. We'll also explain the best use case for each and why.
What is a nested class?
A nested class is a class that is defined and used within another class. There are two types of nested classes:
static nested class
A static nested class is a nested class that is static. A static nested class has the static access modifier in it's declaration.
inner class
Any nested class that isn't static is considered an inner class. In other words, an inner class is any class that doesn't have the static modifier and is defined within another class.
There are two types of inner classes. They are:
Local class
These are inner classes defined inside a method or scope block.
Anonymous class
These are inner classes that are defined as part of an expression.
Let's look at each type of nested class in more detail including examples and suggestions for best use cases...
Static nested classes
A static nested class is any class that's defined within another class and declared static. Specifically, a static nested class abides by the following rules:
- can have any type of access modifier in its declaration (public, private, etc)
- can only access other static members of it's parent class
- can define both static and non-static members
Example of a static nested class:
public class MainClass {
static int mainCount = 0;
public static class NestedClass {
static int classCount = 4;
int instanceCount = 5;
public void printCount(){
System.out.println(mainCount);
}
}
}
Notice how we've defined a static nested class NestedClass within an enclosing MainClass. Remember that the static keyword makes the NestedClass a class member. This means it only has access to other static members of the MainClass but can define both static and non-static members within its own definition.
Since the mainCount variable is static, we are able to reference it inside the printCount() method of our NestedClass. If mainCount was not a static member, we would not be able to reference it within printCount() like we can in this example.
When should I use a static nested class in Java?
Apart from being defined inside another class, a static nested class behaves just like a regular top-level class. It can define both static/non-static members and can only access other static members of it's enclosing class. Use static-nested classes when you are using the class in only one place but want to treat it as a regular class otherwise.
Inner classes
Any nested class that is not static is considered an inner class. Specifically, an inner class abides by the following rules:
- can have any type of access modifier in its declaration (public, private, etc)
- can access both static and non-static members of its parent class
- can only define non-static members
Example of a non-static inner class:
public class MainClass {
static int mainCount = 0;
int pubCount = 0;
public class NestedClass {
int instanceCount = 5;
public void printCount(){
System.out.println(mainCount + pubCount);
}
}
}
Notice how this looks very similar to our first example with a few key differences. Since the NestedClass doesn't include the static keyword, it's considered an inner class. This means we can access both static and non-static members of the parent class MainClass. Notice how we are able to reference both mainCount and pubCount in the printCount() method.
Since inner classes are instance members of their parent class, they can't declare their own static members. If we were to try and declare instanceCount as static, we would get a compile time error.
When should I use an inner class in Java?
Unlike the static nested class, an inner class is able to access both static/non-static members of it's enclosing parent class. Use an inner class when you need to access the enclosing class's members, regardless of whether they are static/non-static.
Also remember that an inner class requires it's parent class to be instantiated before it can be used:
MainClass mC = new MainClass();
MainClass.NestedClass nC = mC.new NestedClass();
If you want to access a nested class without instantiating it's parent first, a static nested class may be a better option...
Local classes
A local class is a type of inner class. A local class is defined and used inside a method or code block. The same rules for inner classes apply to local classes, with the following exception:
- can't have access modifiers in its declaration
Example of a local class:
public class MainClass {
private static int mainCount = 4;
private int pubCount = 2;
public void addCounts(){
class LocalClass {
void addMembers(){
System.out.println(mainCount + pubCount);
}
}
LocalClass lc = new LocalClass();
lc.addMembers();
}
public static void main(String[] args){
MainClass mc = new MainClass();
mc.addCounts(); //prints 6
}
}
Notice how we define a class LocalClass inside of an addCounts() method. Remember that inner classes have access to both static/non-static members of the enclosing class. For this reason, we are able to reference both mainCount and pubcount from within the addMembers() method.
When should I use a local class in Java?
The need for a local class is similar to any inner class. You want to use a local class when you need access to the parent class members (both static and non-static) but only need to use the class within a particular method. Additionally, local classes allow you to modify and access the class constructor. This is an important distinction to anonymous classes, which we will cover next.
Anonymous classes
The anonymous class allows you to declare and instantiate a class at the same time. Unlike a local class, an anonymous class does not have a name assigned to it. The same rules for inner classes apply to anonymous classes with the following exceptions:
- can't have access modifiers in its declaration
- can't define constructors or extend/implement other classes or interfaces
Example of an anonymous class:
public class MainClass {
interface Math {
public void add();
}
Math math = new Math(){
public void add(){
System.out.println(mainCount + pubCount);
}
};
private static int mainCount = 4;
private int pubCount = 2;
public static void main(String[] args){
MainClass mc = new MainClass();
mc.math.add();
}
}
Notice how we first define an interface Math. Instead of explicitly defining a new class with the class keyword, we simply implement the Math interface with an anonymous class. Specifically, we set a variable math of type Math to an anonymous class definition which implements the add() method defined in the interface.
When should I use an anonymous class in Java?
Use the anonymous class when you need functionality but not necessarily a separate entity. The example above demonstrates this concept. Creating a new class that implements the Math interface would be overkill (especially since we are using the math variable in only one place).
For more information on when to use anonymous classes, check out Edayan's notes on anonymous classes. This article explores anonymous classes in depth and provides some great rational for appropriate use cases, etc.
Shadowing
Members of an inner class having the same name as members of their enclosing class are said to "shadow" their enclosing counterparts. This means you can't reference a shadowed declaration by the name itself.
An example of shadowing and inner classes:
public class MainClass {
int myInt = 1;
public class NestedClass {
int myInt = 5;
public void print(){
System.out.println(myInt); //print 5
System.out.println(this.myInt); //prints 5
System.out.println(MainClass.this.myInt); //prints 1
}
}
public static void main(String[] args){
MainClass mc = new MainClass();
MainClass.NestedClass nc = mc.new NestedClass();
nc.print();
}
}
Notice how we have to reference the parent class in MainClass.this.myInt to access the parent class member myInt. Using this inside an inner class references the inner class instance and not the parent class.
Baeldung does a good job of illustrating this concept in his article Nested Classes in Java if you want any additional examples of how shadowing works.
Conclusion
Nested classes provide an easier way to organize your code. While static nested classes behave just like regular classes, inner classes have access to both the non-static/static members of their enclosing class.
There are two types of inner classes, anonymous and local classes. You define local classes within methods and code blocks. You define anonymous classes as expressions that implement a certain abstract class or interface. The main advantage of abstract classes is adding functionality without creating a separate entity that you only use once.
For more information on the benefits of nested classes, be sure to check out Jakob Jenkov's Java Nested Classes.