Nested, Inner, Member, and Top-Level Classes
By darcy on Jun 05, 2007
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.
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.
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
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
method returns one of the
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