Nested Classes in Java
In object-oriented languages, a nested or inner class is a class that’s completely declared within another class.
This allows us to combine classes that are logically bound together, to increase encapsulation, for more concise and maintainable code.
Here’s a quick, non-deep-dive overview of the 4 types of nested classes.
Table of Contents
Static Nested Classes
A nested class is defined like any other class:
Like a static
member, a static
nested class is bound to the class itself, and not an instance of it.
This means we can instantiate it without creating an intermediate instance of Outer
first:
It behaves just like any other class, and follows the same rules:
- All access modifiers are supported
- Both
static
and non-static
members can be defined - Can’t access non-
static
members of its enclosing class
For simpler usage we can even import
the nested class to drop the outer class prefix:
When to Use
static
nested classes behave just like any other top-level class, and should be treated as such. The main advantage is packaging convenience.
Non-Static Nested Classes / Inner Classes
Non-static
nested classes are also known as inner classes:
Instead of being associated with the Outer
class type, the inner Nested
class is bound to instances of its enclosing class.
Thanks to this kind of relationship, it has access to all members of the enclosing class, not just the static
members. But the inner class can’t define any static
members itself, though.
To instantiate the inner class, we now need an instance of its enclosing class:
There’s no longer just a single type, that just happens to be nested under another class. An inner class is tightly bound to the actual instance of its enclosing class, and can’t live on its own anymore.
Serialization
Just because the enclosing class might be serializable, it doesn’t make the nested class automatically serializable, too.
As with any other members, we must ensure that they implement java.io.Serializable
as well.
Or we might end up with a java.io.NotSerializableException
.
When to Use
Inner classes have the advantage of having a deeper connection to their enclosing class, including full access to all of its members. But this connection can lead to non-obvious memory retention. The enclosing class can’t be garbage-collected until the nested instance can, too.
Resources
Inner Classes and Enclosing Instances (JLS)
Local Classes
Local classes are a specialized form of inner classes.
We can define a local class in any kind of block (e.g., methods):
Just like an inner class, we can access all members of the enclosing class. But we can’t provide an access modifier to the class, because it can only be used locally.
When to Use
We can achieve the same behavior with an inner class. But it wouldn’t bind the logic as strongly to a specific block as a local class does.
It’s a great tool for better grouping logic together, and with local classes, we can use the smallest footprint possible.
Resources
Local Class Declarations (JLS)
Anonymous Classes
Anonymous classes are not about defining a nested class, but creating a new class by instantiating a pre-existing type:
We just created a new class based on the interface Runnable
, and because it doesn’t have a name, it’s anonymous.
Not only interfaces can be used for creating an anonymous class. We can also extend other non-final
classes:
A specialized List<String>
implementation without the need to create a separate class altogether, neat!
The creation syntax always follows the same structure:
Anonymous class declarations are expressions and must be a part of a statement, either in a block or as a member declaration itself.
Anonymous Classes Vs. Lambdas
With the advent of lambda expressions we finally got a simpler way of implementing types on the spot:
The functionally of both predicates is identical, so we might think that lambdas are just syntactic sugar for anonymous classes. The generated ByteCode differs, revealing that it might behave the same way, but isn’t done the exactly in the same manner:
The lambda uses the opcode invokedynamic
, which allows for a more dynamic method invocation by the JVM.
When to Use
Anonymous classes are great for small, specific implementations on the spot. Even though a lambda might be sufficient.
Due to their simplicity, there are also multiple downsides compared to local or inner classes:
- Not having a name can make stacktraces harder to follow
- Only a single type can be used, no additional interfaces etc.
- More complex syntax
Resources
- Anonymous Class Declaration (JLS)
- Anonymous classes in the VM (Oracle)
Shadowing
In software-development, shadowing is the re-declaration of a member in a deeper scope. This means we can re-use variable names in nested classes and also access the shadowed member by prefixing the call:
Conclusion
It’s great that we can logically group classes together, or create anonymous instances on the spot. But we need to carefully decide which type of nested class we actually want and need.
Especially for functional interfaces a lambda might be a more concise solution.
Additional Resources
- The Java Tutorials: Nested Classes (Oracle)
- Inner class (Wikipedia)
- Nested Classes in Java (Baeldung)
- Java -Inner Classes (Tutorialspoint)