Rice University - Comp 212 - Intermediate Programming

Spring 2006

Lecture #10 - Using Anonymous Inner Classes Helpers


1. Anonymous Inner Class

Consider the following version of an algorithm to compute the length of a list.
package listFW.visitor;
import listFW.*;

/**
 * Computes the number of elements in a list, using a helper.
 */
public class GetLen implements IListAlgo {

    public static final GetLen Singleton = new GetLen();
    private GetLen() {
    }

    /**
     * Returns 0 because the empty list has no elements.
     * @param host an empty list.
     * @param nu not used.
     * @return Integer(0)
     */
    public Object emptyCase(IEmptyList host, Object... nu) {
        return 0;
    }

    /**
     * Passes the accumulated length (1) to the rest and
     * asks for help to compute the length.
     * @param host a non-empty list.
     * @param nu not used.
     * @return Integer >= 1.
     */
    public Object nonEmptyCase(INEList host, Object... nu) {
        return host.getRest().execute(GetLenHelp.Singleton, 1);
    }
}



GetLenHelp.java
package listFW.visitor;

import listFW.*;

/**
 * Helps computes the number of elements in a list
 * by accumulating the length.
 */
public class GetLenHelp implements IListAlgo {

    public static final GetLenHelp Singleton = new GetLenHelp();
    private GetLenHelp() {
    }

    /**
     * Returns the accumulated length acc because the
     * empty list marks the end of the enclosing list.
     * @param host an empty list.
     * @param acc acc[0] is an Integer, the accumulated length of the preceding list.
     * @return Integer
     */
    public Object emptyCase(IEmptyList host, Object... acc) {
        return acc[0];
    }

    /**
     * Passes the accumulated length (1) to the rest and
     * asks for help to
     * compute the length.
     * @param host a non-empty list.
     * @param acc acc[0] is an Integer, the accumulated length of the preceding list.
     * @return Integer >= 1.
     */

    public Object nonEmptyCase(INEList host, Object... acc) {
        int accLen = 1 + (Integer)acc[0];
        return host.getRest().execute(this, accLen);
    }
}

The GetLenHelp defined in the above will only perform correctly if it is passed the appropriate parameter: the IList  that precedes the host.  Though it is called once, inside of GetLen.nonEmptyCase(...), by the original INEList to :"help" compute and return the length of the host list, we have to go through the standard process of defining a class for it.  Since we only use this helper once, do we really need to have a name for it?  (Aside: Why do we want to name things anyway? )  Is there a way to just create the helper "on-the-fly" without having to give it a name?  The answer is yes.

In Comp 210, Scheme helper functions are best created "on-the-fly" as "local" lambda expressions.  The analogous way in Java to create objects dynamically (i.e. on-the-fly) without having to give names to their classes is to use anonymous inner classes

Whenever we statically (as opposed to dynamically) define a class, we first have to give it a name.  After a (concrete) class is defined, we can then instantiate instances (i.e. objects) of this class by calling new on its constructor(s).  All the classes we have defined so far are named classes.  For most of the time, this mechanism for defining classes is adequate.  However, in order to model more sophisticated systems with changing dynamic behaviors that cannot be a-priori defined, Java provides a programming construct to define a class "anonymously" and instantiate objects for this class on-the-fly.  This is akin to defining  lambda expressions as functions "without names" in Scheme.

The syntax for defining "anonymously" a class and instantiate its object dynamically is as follows.

new SuperClassName(...) {
   local fields;
   overriden method1;
   overriden method2;
   etc...
}
A call to make an anonymous class is always made inside of some class.  As such, the anonymous class is called an "inner" class, and the outer class that contains the class is called the context of the inner class.  This context plays the role analogous to that of the "closure" in a lambda expression.

For example, GetLenHelp can be defined and instantiated anonymously inside of the GetLen.nonEmtpyCase(...) as follows. 
Length.java

package listFW.visitor;

import listFW.*;

/**
 * Computes the length of a list using anonymous inner class.
 */
public class Length implements IListAlgo {

    public static final Length Singleton = new Length();
    private Length() {
    }

    /**
     * Returns 0 because the empty list has no element.
     * @param host an empty list.
     * @param nu not used.
     * @return Integer
     */
    public Object emptyCase(IEmptyList host, Object... nu) {
        return new Integer(0);
    }

    /**
     * Passes the accumulated length (1) to the rest
     * and asks an anonymous inner class for help to
     * compute the length.
     * @param host a non-empty list.
     * @param nu not used.
     * @return Integer
     */
    public Object nonEmptyCase(INEList host, Object... nu) {
        // call helper as an anonymous inner class:
        return host.getRest().execute(new IListAlgo() {
            public Object emptyCase(IEmptyList h, Object... acc) {
                return acc[0];
            }

            public Object nonEmptyCase(INEList h, Object... acc) {
                int accLen = 1 + (Integer)acc[0];
                return h.getRest().execute(this, accLen);
            }
        }// end of anonymous inner class.
        1);  // pass accumulated length of 1 to helper.
    }

}

NOTE: In the above, the parameters of the anonymous inner class are renamed in order to avoid masking the parameter names in the outer object.

2. Inner Classes

Inner classes do not have to be anonymous.  They can be named as well.  Besides fields and methods, a Java class can also contain other classes.  Such classes are called "inner classes".

class X {
    // fields of X ...
    // methods of X ...

    /**
    * named inner class defined inside of X:
    */
    [public | protected | private]  [static]  [final]  [abstract]  class Y [ extends A] [implements B] {
        // fields of Y ...
        // methods of Y ...
        // classes of Y ...
  }
}

Access specifier:

Just like any other class, a class defined inside of another class can be public, protected, package private, or private.

Scope specifier:

Just like any other class, a class defined inside of another class can be static or non-static.

When it is defined as static, it is called a nested class. The members (i.e. fields, methods, classes) of a (static) nested class can access to only static members of the enclosing class.

When it is non-static, it is called an inner class. The members of an inner class can access ALL members of the enclosing class. The enclosing class (and its enclosing class, if any, and so on) contains the environment that completely defines the inner class and constitutes what is called the closure of the inner class.  As all functional programmers should know, closure is a powerful concept.  One of the greatest strength in the Java programming language is the capability to express closures via classes with inner classes.  We shall see many examples that will illustrate this powerful concept during the rest of the semester.

Extensibility Specifier:

Just like a regular class, a final  nested/inner class cannot extended.

Abstract Specifier:

Just like a regular class, an abstract nested/inner class cannot be instantiated.

Inheritance Specifier:

Just like a regular class, an nested/inner can extend any non-final class and implement any number of interfaces that are within its scope.

Usage:

Nested classes are used mostly to avoid name clash and to promote and enforce information hiding.  Examples?

Inner classes are used to create objects that have direct access to the internals of the outer object and perform complex tasks that simple methods cannot do.  For examples, "event listeners" for a Java GUI components are implemented as inner classes.  The dynamic behavior and versatility of these "listeners" cannot be achieved by the addition of a set of fixed methods to a GUI component.  We shall study Java event handling soon!

An inner object can be thought as an extension of the outer object.


3. Closure 

In functional programming, the closure of a function (lambda) consists of the function itself and an environment in which the function is well-defined.  In Java, a function is replaced by a class.  An inner class is only defined in the context of its outer object (and the outer object of the outer object, etc...).  An inner class together with its nested sequence of outer objects in which the inner class is well-defined is the equivalent of the notion of closure in functional programming.  Such a notion is extremely powerful.  Just like knowing how to effectively use use lambda expressions and higher order functions is key to writing powerful functional programs in Scheme, effective usage of anonymous inner classes is key to writing powerful OO programs in Java.

Exercise: Consider a list of Integers.  Write a visitor to move the minimum element to the front of the list while maintaining the ordering of the remaining elements.

Click here for a solution.


4. Access Specifiers

The following table describes who can use a field with a given access specifier.
 
the same class the same package,
but different class
a subclass any class, anywhere
within the program
private yes no no no
"package private" yes yes no no
protected yes yes (but only
in subclasses)
yes no
public yes yes yes yes

Consider the following class definition.

class MyInteger {
  private int iAmPrivate;

  boolean isEqualTo(MyInteger anotherInteger) {
    return iAmPrivate == anotherInteger.iAmPrivate;
  }
}

It illustrates an important point about the meaning of private.  Objects of the same type have access to one another's private and protected members.  This is because access restrictions apply at the class or type level (all instances of a class) rather than at the object level (this particular instance of a class).


dxnguyen at rice.edu
Copyright 2005, Alan Cox and Dung X. Nguyen - All rights reserved.