Tuesday Jun 05, 2007
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.
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 [Link] 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 [Link] 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:
• getEnclosingClass
• getEnclosingConstructor
• getEnclosingMethod
• isAnonymousClass
• isLocalClass
• isMemberClass
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, [Link] 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 [Link].* class on a javax.* package.
Posted at 05:11PM Jun 05, 2007 by darcy in Java | Comments[7] | Permalink
Comments:
Very nice explanation, thanks!
Posted by A. Sundararajan on June 05, 2007 at 09: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 06:22 PM PDT #
Howard,
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 07: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 08: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
[Link]. 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 10: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 07, 2007 at 12:00 AM 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 09:11 AM PDT