There has been an interesting discussion on reddit, the other day
Static Inner Classes. When is it too much?
First, let’s review a little bit of basic historic Java knowledge.
Java-the-language offers four levels of nesting classes, and by “Java-the-language”, I mean that these constructs are mere “syntax sugar”. They don’t exist in the JVM, which only knows ordinary classes.
(Static) Nested classes
class Outer {
static class Inner {
}
}
In this case,
Inner is completely independent of
Outer, except for a common, shared namespace.
Inner classes
class Outer {
class Inner {
}
}
In this case,
Inner instances have an implicit reference to their enclosing
Outer instance. In other words, there can be no
Inner instance without an associated
Outer instance.
The Java way of creating such an instance is this:
Outer.Inner yikes = new Outer().new Inner();
What looks totally awkward makes a lot of sense. Think about creating an
Inner instance somewhere inside of
Outer:
class Outer {
class Inner {
}
void somewhereInside() {
// We're already in the scope of Outer.
// We don't have to qualify Inner explicitly.
Inner aaahOK;
// This is what we're used to writing.
aaahOK = new Inner();
// As all other locally scoped methods, we can
// access the Inner constructor by
// dereferencing it from "this". We just
// hardly ever write "this"
aaahOK = this.new Inner();
}
}
Note that much like the
public or
abstract keywords, the
static keyword is implicit for nested interfaces. While the following hypothetical syntax might look familiar at first sight…:
class Outer {
<non-static> interface Inner {
default void doSomething() {
Outer.this.doSomething();
}
}
void doSomething() {}
}
… it is not possible to write the above. Apart from the lack of a
<non-static> keyword, there don’t seem to be any obvious reason why “inner interfaces” shouldn’t be possible. I’d suspect the usual – there must be some really edge-casey caveat related to backwards-compatibility and/or multiple inheritance that prevents this.
Local classes
class Outer {
void somewhereInside() {
class Inner {
}
}
}
Local classes are probably one of the least known features in Java, as there is hardly any use for them. Local classes are named types whose scope extends only to the enclosing method. Obvious use-cases are when you want to reuse such a type several times within that method, e.g. to construct several similar listeners in a JavaFX application.
Anonymous classes
class Outer {
Serializable dummy = new Serializable() {};
}
Anonymous classes are subtypes of another type with only one single instance.
Top 5 Use-Cases For Nested Classes
All of anonymous, local, and inner classes keep a reference to their enclosing instance, if they’re not defined in a static context. This may cause a lot of trouble if you let instances of these classes leak outside of their scope. Read more about that trouble in our article:
Don’t be Clever: The Double Curly Braces Anti Pattern.
Often, however, you do want to profit from that enclosing instance. It can be quite useful to have some sort of “message” object that you can return without disclosing the actual implementation:
class Outer {
// This implementation is private ...
private class Inner implements Message {
@Override
public void getMessage() {
Outer.this.someoneCalledMe();
}
}
// ... but we can return it, being of
// type Message
Message hello() {
return new Inner();
}
void someoneCalledMe() {}
}
With (static) nested classes, however, there is no enclosing scope as the
Inner instance is completely independent of any
Outer instance. So what’s the point of using such a nested class, rather than a top-level type?
1. Association with the outer type
If you want to communicate to the whole world, hey, this (inner) type is totally related to this (outer) type, and doesn’t make sense on its own, then you can nest the types. This has been done with
Map and
Map.Entry, for instance:
public interface Map<K, V> {
interface Entry<K, V> {
}
}
2. Hiding from the outside of the outer type
If package (default) visibility isn’t enough for your types you can create
private static classes that are available only to their enclosing type and to all other nested types of the enclosing type. This is really the main use-case for static nested classes.
class Outer {
private static class Inner {
}
}
class Outer2 {
Outer.Inner nope;
}
3. Protected types
This is really a very rare use-case, but sometimes, within a class hierarchy, you need types that you want to make available only to subtypes of a given type. This is a use-case for
protected static classes:
class Parent {
protected static class OnlySubtypesCanSeeMe {
}
protected OnlySubtypesCanSeeMe someMethod() {
return new OnlySubtypesCanSeeMe();
}
}
class Child extends Parent {
OnlySubtypesCanSeeMe wow = someMethod();
}
4. To emulate modules
Unlike Ceylon, Java doesn’t have first-class modules. With Maven or OSGi, it is possible to add some modular behaviour to Java’s build (Maven) or runtime (OSGi) environments, but if you want to express modules in code, this isn’t really possible.
However, you can establish modules by convention by using static nested classes. Let’s look at the
java.util.stream package. We could consider it a module, and within this module, we have a couple of “sub-modules”, or groups of types, such as the internal
java.util.stream.Nodes class, which roughly looks like this:
final class Nodes {
private Nodes() {}
private static abstract class AbstractConcNode {}
static final class ConcNode {
static final class OfInt {}
static final class OfLong {}
}
private static final class FixedNodeBuilder {}
// ...
}
Some of this
Nodes stuff is available to all of the
java.util.stream package, so we might say that the way this is written, we have something like:
- a synthetic
java.util.stream.nodes sub-package, visible only to the java.util.stream “module”
- a couple of
java.util.stream.nodes.* types, visible also only to the java.util.stream “module”
- a couple of “top-level” functions (static methods) in the synthetic
java.util.stream.nodes package
Looks a lot like Ceylon, to me!
5. Cosmetic reasons
The last bit is rather boring.
Or some may find it interesting. It’s about taste, or ease of writing things. Some classes are just so small and unimportant, it’s just easier to write them inside of another class. Saves you a
.java file. Why not.
Conclusion
In times of Java 8, thinking about the very old features of Java the language might not prove to be extremely exciting. Static nested classes are a well-understood tool for a couple of niche use-cases.
The takeaway from this article, however, is this. Every time you nest a class, be sure to make it
static if you don’t absolutely need a reference to the enclosing instance.
You never know when that reference is blowing up your application in production.