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.
Static Nested Classes
A nested class is defined like any other class:
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
It behaves just like any other class, and follows the same rules:
- All access modifiers are supported
staticmembers can be defined
- Can’t access non-
staticmembers 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
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.
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
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.
Inner Classes and Enclosing Instances (JLS)
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.
Local Class Declarations (JLS)
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-
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:
// ANONYMOUS CLASS 0: new #2 // class Anonymous$1 3: dup 4: invokespecial #3 // Method Anonymous$1."<init>":()V 7: astore_1 8: return // LAMBDA 0: invokedynamic #2, 0 // InvokeDynamic #0:test:()Ljava/util/function/Predicate; 5: astore_1 6: return
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
- Anonymous Class Declaration (JLS)
- Anonymous classes in the VM (Oracle)
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:
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.
- The Java Tutorials: Nested Classes (Oracle)
- Inner class (Wikipedia)
- Nested Classes in Java (Baeldung)
- Java -Inner Classes (Tutorialspoint)