Nested, Inner, Member, and Top-Level Classes

One way declared types in Java differ from one another is whether the type is a class (which includes enums) or an interface (which includes annotation types). An independent property of a type is its relation to the surrounding lexical context. A top level class does not appear inside another class or interface. If a type is not top level it is nested. However, there are a number of distinct ways a type can be nested. First, a type can be a member of another type; a member type is directly enclosed by another type declaration. All member types have names; however, some member types are inner classes and others are not. If a type is explicitly or implicitly static, it is not an inner class; all member interfaces are implicitly static.

Inner classes also include local classes, which are named classes declared inside of a block like a method or constructor body, and anonymous classes, which are unnamed classes whose instances are created in expressions and statements. Anonymous classes are used to implement specialized enum constants. Inner classes have access to instance variables of any lexically enclosing instance; that is, the fields of the object an inner class is created in reference to. However, not all inner classes have enclosing instances; inner classes in static contexts, like an anonymous class used in a static initializer block, do not.

The Venn diagram below shows how these distinctions relate and combine; in particular, member-ness and inner-ness are not orthogonal properties.

Venn Diagram of Class Nesting Kinds

pdf of diagram

A reflective API providing a complete model of the language needs to allow these differences to be determined. Two reflective APIs providing this information are core reflection as of JDK 5 and javax.lang.model from JSR 269 in JDK 6; however, each API exposes the data differently. (The legacy apt mirror API finesses the issue by not modeling local and anonymous classes.)

Core reflection uses java.lang.Class to model types. When inner classes were introduced back in JDK 1.1, a getDeclaringClass method was added to Class. While this supports member types, relevant information about local and anonymous classes was not directly available. To remedy this, JDK 5 added a number of methods to return the enclosing entity, if any, and identify what kind of nesting a type may have:

Because of the lack of an usable supertype, the different kinds of enclosing elements (classes, methods, and constructors) must be retrieved from different methods. If the class is not so enclosed, a null is returned. Therefore the code to find the enclosing element is a sequence of if-methodA-not-equal-null-else-if-methodB-not-equal-null tests.

Starting with a clean slate, javax.lang.model was able to provide a cleaner way of modeling these distinctions. First, the functionality of getDeclaringClass and the three getEnclosingFoo methods is provided by the single getEnclosingElement method, which returns the immediately lexically enclosing element regardless of whether it is a class, or method, or constructor. Second, for types the getNestingKind method returns one of the NestingKind enum constants, ANONYMOUS, LOCAL, MEMBER, or TOP_LEVEL. Those constants clearly correspond to the possible alternatives displayed in the diagram above. While it would be technically possible to add a getNestingKind method to Class, that would create an undesired dependency of a java.lang.\* class on a javax.\* package.


Very nice explanation, thanks!

Posted by A. Sundararajan on June 05, 2007 at 02:44 PM PDT #

This is a very well written explanation - congratulations. I have wanted the relationship simplified in that the distinction between an anonymous class and a member class doesn't seem useful to me. I know the JLS makes a distinction, but I think that it would be better if anonymous classes were classed as members of their enclosing class. IE:
class Example {
  class ExampleFirst {}
  Object dollarFirst = new Object() {}; // declares instance of an anonymous class, the class's 'real' name is Example$1
I am suggesting that Example$1 is a member class of Example, like ExampleFirst is.

Posted by Howard Lovatt on June 06, 2007 at 11:22 AM PDT #


Glad you found the explanation helpful. Much of the discussion of member types in the JLS deals with their names and how the names interact with other named entities. Since anonymous classes don't have names at the source level, they don't play in this space.

The binary names of member types have always been defined because they need a standard linkage convention to be able to be called from other classes. However, until JLSv3, the binary names of anonymous types have not been defined, even though Example$1 is traditional. Since anonymous classes can't be referred to directly from other source files, their linkage conventions are a compiler-internal contract and didn't have to be of the form Example$n. In part to help support the new reflection methods in JDK 5, the names of anonymous classes are now constrained to the traditional form, see JLSv3 section 13.1.

Posted by Joe Darcy on June 06, 2007 at 12:39 PM PDT #

I am specifically suggesting that the NestingKind enum doesn't have the value ANONYMOUS, so that if the anonymous class is within a class it is a MEMBER and if it is within a constructor or method it is a LOCAL.

I find that I am always having to make special cases for anonymous classes that then detect if the anonymous class is a local or a member and then call the handlers for local or member as appropriate. For the code I have written not having ANONYMOUS as a separate kind would simplify the code. This change is also consistent with getEnclosingElement that returns executable or type elements for the enclosing lexical scope.

Posted by Howard Lovatt on June 06, 2007 at 01:58 PM PDT #

Ah, I see. As a language model, when the JLS specifies an answer to a question, that is the answer we generally return. Nesting kind is not defined as a concept in the JLS and we had discussions over what to call this concept because of that omission. However, given that member, local, and anonymous classes are all defined in the JLS and we've chosen to present those distinctions with NestingKind, I believe anonymous classes must be identified as NestingKind.ANONYMOUS. The Java language can certainly be complicated in places and any complete model will share in that complexity. For example, anonymous classes can be used in static contexts, like a static initializer, where they don't have an enclosing instance.

Are you computing an "effective nesting-ness" before switching on the enum value?

Posted by Joe Darcy on June 06, 2007 at 03:03 PM PDT #

Paraphrasing the code that I write: I might have something like a switch on the kind and deal with local, member, and top level differently. When I get an anonymous class I then use getEnclosingElement and have something like a switch on the element type returned (e.g. via instanceof tests). Whilst this works it is annoying and I can't see an advantage in making anonymous a special case - its only difference is that it doesn't have a name.

With regard to your specific examples:

  1. A class inside a static initializer I would treat as a local

  2. The class of an enum instance I would treat as a member

I do agree that the JLS is not helpful on this point, in particular the distinction it makes between anonymous and other inner classes seems to serve no purpose. It just makes the explanation contorted.

Posted by Howard Lovatt on June 06, 2007 at 05:00 PM PDT #

A note on writing annotation processors, as mentioned in the documentation, it is not necessarily reliable to use instanceof tests to determine what sort of element an object represents. The API was designed so that the compiler's internal model and the JSR 269 model don't have to be the same; for example, all of the Element modeling interfaces can be implemented by the same class. A visitor or kind check should be used instead.

Posted by Joe Darcy on June 07, 2007 at 02:11 AM PDT #

Post a Comment:
Comments are closed for this entry.



« July 2016

No bookmarks in folder