Core Java

Top 5 Use-Cases For Nested Types

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.

Reference: Top 5 Use-Cases For Nested Types from our JCG partner Lukas Eder at the JAVA, SQL, AND JOOQ blog.

Lukas Eder

Lukas is a Java and SQL enthusiast developer. He created the Data Geekery GmbH. He is the creator of jOOQ, a comprehensive SQL library for Java, and he is blogging mostly about these three topics: Java, SQL and jOOQ.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button