The Java programming course


Lecture 5: Nested Classes and Interfaces

Classes and interfaces can be declared inside other classes and interfaces, either as members or within blocks of code. These nested classes and nested interfaces can take a number of different forms, each with its own properties.

The ability to define nested types serves two main purposes. First, nested classes and nested interfaces allow types to be structured and scoped into logically related groups. Second, and more important, nested classes can be used to connect logically related objects simply and effectively.

Static Nested Types

A nested class or interface that is declared as a static member of its enclosing class or interface acts just like any non-nested, or top-level, class or interface, except that its name and accessibility are defined by its enclosing type. The name of a nested type is expressed as EnclosingName.NestedName. The nested type is accessible only if the enclosing type is accessible.

Static Nested Classes

The static nested class is the simplest form of nested class. You declare one by preceding the class declaration with the static modifier. When nested in an interface, a class declaration is always static and the modifier is, by convention, omitted. A static nested class acts just like any top-level class. It can extend any other class (including the class it is a member of), implement any interface and itself be used for further extension by any class to which it is accessible. It can be declared final or abstract.

Static nested classes serve as a mechanism for defining logically related types within a context in which that type makes sense. For example, consider a Permissions class that bears information about a BankAccount object. Because the Permissions class is related to the contract of the BankAccount class - it is how a BankAccount object communicates a set of permissions - it is a good candidate to be a nested class:

public class BankAccount { private long number; // account number private long balance; // current balance (in cents) public static class Permissions { public boolean canDeposit, canWithdraw, canClose; } // ... }

The Permissions class is defined inside the BankAccount class, making it a member of that class. When permissionsFor returns a Permissions object, it can refer to the class simply as Permissions in the same way it can refer to balance without qualification: Permissions is a member of the class. The full name of the class is BankAccount.Permissions. This full name clearly indicates that the class exists as part of the BankAccount class, not as a stand-alone type. Code outside the BankAccount class must use the full name, for example:

BankAccount.Permissions perm = acct.permissionsFor(owner);

If BankAccount were in a package named bank, the full name of the class would be bank.BankAccount.Permissions.

Nested Interfaces

Nested interfaces are also always static and again, by convention the static modifier is omitted from the interface declaration. They serve simply as a structuring mechanism for related types. When we look at non-static nested classes you will see that they are inherently concerned with implementation issues. Since interfaces do not dictate implementation they cannot be non-static.

Inner Classes

Non-static nested classes are called inner classes. Non-static class members are associated with instances of a class - non-static fields are instance variables and non-static methods operate on an instance. Similarly, an inner class is also associated with an instance of a class, or more specifically an instance of an inner class is associated with an instance of its enclosing class - the enclosing instance or enclosing object.

You often need to closely tie a nested class object to a particular object of the enclosing class. Consider, for example, a method for the BankAccount class that lets you see the last action performed on the account, such as a deposit or withdrawal:

public class BankAccount { private long number; // account number private long balance; // current balance (in cents) private Action lastAct; // last action performed public class Action { private String act; private long amount; Action (String act, long amount) { this.act = act; this.amount = amount; } public String toString () { // identify our enclosing account return number + ": " + act + " " + amount; } } public void deposit (long amount) { balance += amount; lastAct = new Action("deposit", amount); } public void withdraw (long amount) { balance -= amount; lastAct = new Action("withdraw", amount); } // ... }

The class Action records a single action on the account. It is not declared static, and that means its objects exist relative to an object of the enclosing class.

The relationship between an Action object and its BankAccount object is established when the Action object is created, as shown in the deposit and withdraw methods. When an inner class object is created, it must be associated with an object of its enclosing class. Usually, inner class objects are created inside instance methods of the enclosing class, as in deposit and withdraw. When that occurs the current object this is associated with the inner object by default. The creation code in deposit is the same as the more explicit:

lastAct = this.new Action("deposit", amount);

Any BankAccount object could be substituted for this. For example, suppose we add a transfer operation that takes a specified amount from one account and places it in the current account - such an action needs to update the lastAct field of both account objects:

public void transfer (BankAccount other, long amount) { other.withdraw(amount); deposit(amount); lastAct = this.new Action("transfer", amount); other.lastAct = other.new Action("transfer", amount); }

In this case we bind the second Action object to the other BankAccount object and store it as the last action of the other account. Each BankAccount object should only refer to Action objects for which that BankAccount object is the enclosing instance. It would make no sense above, for example, to store the same Action object in both the current lastAct field and other.lastAct.

An inner class declaration is just like a top-level class declaration except for one restriction - inner classes cannot have static members (including static nested types), except for final static fields that are initialized to constants or expressions built up from constants.

Accessing Enclosing Objects

The toString method of Action directly uses the number field of its enclosing BankAccount object. A nested class can access all members of its enclosing class - including private fields and methods - without qualification because it is part of the enclosing class's implementation. An inner class can simply name the members of its enclosing object to use them. The names in the enclosing class are all said to be in scope. The enclosing class can also access the private members of the inner class, but only by an explicit reference to an inner class object such as lastAct. While an object of the inner class is always associated with an object of the enclosing class, the converse is not true. An object of the enclosing class need not have any inner class objects associated with it, or it could have many.

When deposit creates an Action object, a reference to the enclosing BankAccount object is automatically stored in the new Action object. Using this saved reference, the Action object can always refer to the enclosing BankAccount object's number field by the simple name number, as shown in toString. The name of the reference to the enclosing object is this preceded by the enclosing class namea form known as qualified-this. For example, toString could reference the number field of the enclosing BankAccount object explicitly:

return BankAccount.this.number + ": " + act + " " + amount;

The qualified-this reference reinforces the idea that the enclosing object and the inner object are tightly bound as part of the same implementation of the enclosing class. This is further reinforced by the qualified-super reference, which allows access to members of the enclosing instance's superclass that have been hidden, or overridden, by the enclosing class.

A nested class can have its own nested classes and interfaces. References to enclosing objects can be obtained for any level of nesting in the same way: the name of the class and this. If class X encloses class Y which encloses class Z, code in Z can explicitly access fields of X by using X.this.

Extending Inner Classes

An inner class can be extended just as any static nested class or top-level class can. The only requirement is that objects of the extended class must still be associated with objects of the original enclosing class or a subclass. Usually this is not a problem because the extended inner class is often declared within an extension of the outer class:

class Outer { class Inner {} } class ExtendedOuter extends Outer { class ExtendedInner extends Inner {} Inner ref = new ExtendedInner(); }

If the enclosing class of the inner subclass is not a subclass of Outer, or if the inner subclass is not itself an inner class, then an explicit reference to an object of Outer must be supplied when the Inner constructor is invoked via super. For example:

class Unrelated extends Outer.Inner { Unrelated (Outer ref) { ref.super(); } }

An inner class can extend another, unrelated, inner class provided an appropriate enclosing instance is supplied to the superclass, as just described. The resulting inner class then has two enclosing instancesone for the extended class and one for the superclass. Such designs are convoluted, however, and are best avoided.

Inheritance, Scoping, and Hiding

Within an inner class, all names declared within the enclosing class are said to be in scope - they can be used as if the inner class code were declared in the outer class. An inner class's own fields and methods (and nested types) can hide those of the enclosing object. There are two ways in which this can occur:

  • a field or method is declared in the inner class;
  • a field or method is inherited by the inner class.

In both cases any use of the simple name refers to the member of the inner class, whether declared or inherited. The enclosing object's field or method must be accessed explicitly using a qualified-this expression.

In the second case, the use of the simple name can mislead a reader of the code. Consider the following:

class Host { int x; class Helper extends Unknown { void increment () { x++; } // Not what you think! } }

The increment method appears to increment the x field of the enclosing Host instance. In fact, Unknown also declares a field x and that field is inherited by Helper. The inherited x field hides the x field from the enclosing scope, and so it is the inherited x field that is incremented not the field that is seen in the source code! To avoid giving the reader the wrong impression about what the code is doing, you should avoid the use of simple names in this situation. Instead, the reference to x should be explicitly qualified as either this.x or Host.this.x, depending on which field it is meant to refer to.

An inner class method with the same name as an enclosing class method hides all overloaded forms of the enclosing class method, even if the inner class itself does not declare those overloaded forms. For example:

class Outer { void print () {} void print (int val) {} class Inner { void print () {} void show () { print(); Outer.this.print(); print(1); // INVALID: no Inner.print(int) } } }

Here the declaration of Inner.print hides all forms of Outer.print. When Inner.show invokes print(1), the compiler reports that Inner has no method print that takes an integer argument. The show method must explicitly qualify the method invocation with Outer.this. There is a good reason to do this. Normally, a set of overloaded methods within a class have the same basic contract - they just operate on different parameter types. If an inner class method happens to have the same name as an enclosing class method, there is no reason to assume that it supports the same contract - consequently, the hiding of the enclosing class's overloaded forms prevents a completely unrelated method from being accidentally invoked. As usual, there are few reasons to hide fields or methods in this way, so the issue is best avoided in the first place.

Local Inner Classes

You can define inner classes in code blocks, such as a method body, constructor, or initialization block. These local inner classes are not members of the class of which the code is a part but are local to that block, just as a local variable is. Such classes are completely inaccessible outside the block in which they are defined - there is simply no way to refer to them. But instances of such classes are normal objects that can be passed as arguments and returned from methods, and they exist until they are no longer referenced. Because local inner classes are inaccessible, they can't have access modifiers, nor can they be declared static because, well, they are local inner classes.

A local inner class can access all the variables that are in scope where the class is definedlocal variables, method parameters, instance variables (assuming it is a non-static block), and static variables. The only restriction is that a local variable or method parameter can be accessed only if it is declared final. The reason for this restriction relates mainly to multithreading issues and ensures that all such variables have well-defined values when accessed from the inner class. Given that the method accessing the local variable or parameter could be invoked after the completion of the method in which the local class was definedand hence the local variables and parameters no longer existthe value of those variables must be frozen before the local class object is created. If needed, you can copy a non-final variable into a final one that is subsequently accessed by the local inner class.

Consider the standard interface Iterator defined in the java.util package. This interface defines a way to iterate through a group of objects. It is commonly used to provide access to the elements in a container object but can be used for any general-purpose iteration:

package java.util; public interface Iterator<E> { boolean hasNext (); E next () throws NoSuchElementException; void remove () throws UnsupportedOperationException, IllegalStateException; }

The hasNext method returns true if next has more elements to return. The remove method removes from the collection the last element returned by next. But remove is optional, which means an implementation is allowed to throw UnsupportedOperationException. If remove is invoked before next, or is invoked multiple times after a single call to next, then IllegalStateException is thrown. NoSuchElementExceptionalso part of the java.util packageis thrown if next is invoked when there are no more elements.

Here is a simple method that returns an Iterator to walk through an array of Object instances:

public static Iterator<Object> walkThrough (final Object[] objs) { class Iter implements Iterator<Object> { private int pos = 0; public boolean hasNext () { return pos<objs.length; } public Object next () throws NoSuchElementException { if (pos>=objs.length) throw new NoSuchElementException(); return objs[pos++]; } public void remove () throws UnsupportedOperationException { throw new UnsupportedOperationException(); } } return new Iter(); }

The Iter class is local to the walkThrough method; it is not a member of the enclosing class. Because Iter is local to the method, it has access to all the final variables of the method - in particular the parameter objs. It defines a pos field to keep track of where it is in the objs array.

Members of local inner classes can hide the local variables and parameters of the block they are declared in, just as they can hide instance fields and methods.

Anonymous Inner Classes

When a local inner class seems too much for your needs, you can declare anonymous classes that extend a class or implement an interface. These classes are defined at the same time they are instantiated with new. For example, consider the walkThrough method. The class Iter is fairly lightweight and is not needed outside the method. The name Iter doesn't add much value to the code - what is important is that it is an Iterator object. The walkThrough method could use an anonymous inner class instead:

public static Iterator<Object> walkThrough (final Object[] objs) { return new Iterator<Object>() { private int pos = 0; public boolean hasNext () { return pos<objs.length; } public Object next () throws NoSuchElementException { if (pos>=objs.length) throw new NoSuchElementException(); return objs[pos++]; } public void remove () throws UnsupportedOperationException { throw new UnsupportedOperationException(); } }; }

Anonymous classes are defined in the new expression itself, as part of a statement. The type specified to new is the supertype of the anonymous class. Because Iterator is an interface, the anonymous class in walkThrough implicitly extends Object and implements Iterator. An anonymous class cannot have an explicit extends or implements clause, nor can it have any modifiers.

Anonymous inner classes cannot have explicit constructors declared because they have no name to give the constructor. If an anonymous inner class is complex enough that it needs explicit constructors then it should probably be a local inner class. In practice, many anonymous inner classes need little or no initialization. In either case, an anonymous inner class can have initializers and initialization blocks that can access the values that would logically have been passed as a constructor argument. The only construction problem that remains is the need to invoke an explicit superclass constructor. To solve this problem the new expression is written as if a superclass instance were being constructed, and that constructor is invoked as the superclass constructor.


References
  • K.Arnold, J.Gosling, D.Holmes: The Java programming language. Fourth edition. Section 5: nested classes and interfaces.