Sunday, April 08, 2012

Inner Classes in Java


Overview

A class that is defined inside another class is called an inner class. Inner classes have been available in Java since 1.1. 

While it's possible to implement most, if not all, of the functionality provided by inner classes without using them, they do have some important benefits:
  • Some design patterns are easily implemented with inner classes, e.g., adaptor, enumerator and state patterns.
  • Inner classes provide an elegant and convenient way of implementing callback mechanism.
  • Closures (with some restrictions) can be implemented in Java using inner classes.
  • Multiple inheritance can be emulated by using inner classes.

Tip: Inner classes are implemented fully in the Java compiler.

Note: An inner class cannot be instantiated without an instance of the enclosing class.

Note: Inner classes cannot define static members. The only exception to this rule is compile time static constants that are marked final.

Note: A class defined inside another class that is marked static (i.e., a static member class) is not an inner class.

Types of Inner Classes

Inner classes are of the following types:


Member Inner Classes

A member inner class is defined inside another class the same way a member field or method is defined, with any of the supported access modifiers. 

public class Outer {
     …

     public class Inner {
          … 
     }
}

Instantiating a member inner class is a little different than instantiating a regular class. The general syntax to create an instance of a member class is as follows:

outerClassReference.new MemberClassConstructor();

Give the class definitions above, here's how you'll instantiate an instance of the Inner class in the context of an instance of the Outer class:

Outer outer = new Outer();
Outer.Inner inner = outer.new Outer();

Warning: Note that you always use the constructor name for the member inner class after the new operator. Since the new operator is already qualified with the enclosing instance reference (as in out.new), the Java compiler figures out the fully qualified name of the enclosing class name automatically. It is a compile-time error to qualify the inner class constructor with its outer class name while creating an instance of an inner class.


Local Inner Classes

A local inner class is defined inside a code block (method, instance initializer or static initializer) with its
scope limited to the enclosing block. Because the scope of the class is defined by the enclosing block, it cannot use any of the access modifiers.

public class Outer {
     … 
     public static void main(String[] args) {
          class Inner {
               … 
          }
          
          … 
     }
}

A local inner classes can be instantiated just as any other class by simply new'ing it up.

Note: In order for a local inner class to use a variable from its enclosing scope, the captured variable must be declared final or the compiler will complain.


Anonymous Inner Classes

An anonymous class is the same as a local inner class except that it doesn't have a name. An interesting side effect of an anonymous class not having a name is that it cannot declare a constructor. An anonymous class is a one-shot class that's defined (by implementing an interface or extending a class) and instantiated at the same time as follows:

public lass Task {
     public void execute() {
          Thread thread = new Thread(new Runnable() {
               … 
          });
          thread.start();
     }
}

An anonymous inner class is defined and instantiated at the same time.

Note: In order for a local inner class to use a variable from its enclosing scope, the captured variable must be declared final or the compiler will complain.

Accessing Enclosing Class Members

An inner class has access to all instance members (fields and methods) of its enclosing class. When accessing non-hidden members, i.e., members defined only in the enclosing class, you can access them directly. However, if the inner class hides a member of the enclosing class, you must qualify access to the enclosing class member using the Outer.this.member syntax. An instance variable from the enclosing class can be access in any of the following ways if it's not hidden:
  1. Simple name,
  2. Simple name qualified with the keyword this, or
  3. Simple name qualified with the enclosing class name and the keyword this -- e.g. Outer.this.member
However, when accessing a hidden variable, you must qualify the this keyword with the name of the outer class.

Here's a quick example:

public class Outer {
     private int value;

     public class Inner {
          private int value;

          public void printValue() {
               System.out.println("inner value: " + value);
               System.out.println("inner value: " + this.value);
               System.out.println("outer value: " + Outer.this.value);
          }
     }
}

Summary

Inner classes are a powerful Java feature that affords programmers to achieve things as multiple inheritance (emulated) and closures. It also provides an elegant mechanism for implementing certain design patterns. An inner class can be defined as member of another class, within a local scope (method, instance and static initializers), or as anonymous implementation of a given interface or class. When using inner classes pay special attention to some of the restrictions they place on what they can define and what/how they can access members of the enclosing type.

No comments: