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.

4 thoughts on “Top 5 Use-Cases For Nested Types

  1. “Apart from the lack of a 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.”

    Not really. Non-static inner interfaces are not possible because, well … how to say it… because they are not possible. What on earth would a non static interface be? Have in every instance of it a reference to an instance of the enclosing class? Actually an interface can do that even if it is static. In every instance it has (it does not actually have any, it is an interface) it has a reference to just anything you like. This way there is no static and non-static inner/nested interfaces. There are just interfaces inside other interfaces or inside classes. Mostly harmless.

    1. Everything is possible, but is it reasonable?

      What on earth would a non static interface be? Have in every instance of it a reference to an instance of the enclosing class?

      What else would it be? :)

      I sense a strong opposition in your words against the thought of this kind of implicit state in interfaces. I share the same opposition, and I don’t think it is needed really. But then again, you gave me no formal reasons why it shouldn’t be possible. An inner interface could impose an enclosing instance as a part of its contract to all implementations. This made no sense at all before default methods. It makes a little more sense now.

      1. What do you mean “impose”? And what do you mean enclosing? An instance enclosing a class/interface? Inner class instances are created by an enclosing instance. Inner classes themselves are loaded by the class loader and are not impose anything on an enclosing instance. Interfaces do not have any instances, that is their nature. Even if there are default methods defined inside.

        btw: Groovy does not allow you inner classes only nested ones.

        1. By “impose”, I mean that an inner interface would “impose” a reference to its enclosing instance to all implementors. Example:

          class Outer {
              <non-static> interface Inner {
                  default void x() {
                      System.out.println(Outer.this.toString());
                  }
              }
          
              Inner2 y() {
                  return new Inner2();
              }
          }
          
          class Inner2 implements Inner {
          }
          
          // This would now print Outer.toString()
          // to the console
          new Outer().y().x();
          

          If it were possible, the above would be what would happen. If it is useful, I somewhat doubt it.

          It makes sense for Groovy not to support all of this inner class mess. Keeps things simple, and more concise as people won’t ask silly questions as I did.

Leave a Reply