Rice University - Comp 212 - Intermediate Programming

Fall 2002

Lecture #09 - Using Anonymous Inner Classes Helpers; Composite Design Pattern


Today's menu:

  1. Announce homework 2.
  2. Anonymous inner classes and Closure
  3. Introduction to the Composite Design Pattern (if time permits)

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.
 * @author D. X. Nguyen
 * @since Copyright 2002 - DXN All rights reserved
 */
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 new Integer(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
     */
    public Object nonEmptyCase(INEList host, Object nu) {
        return host.getRest().execute(GetLenHelp.Singleton, new Integer(1));
    }
}

GetLenHelp.java
Created with JBuilder
package listFW.visitor;

import listFW.*;

/**
 * Helps computes the number of elements in a list
 * by accumulating the length.
 * @author D. X. Nguyen
 * @since Copyright 2002 - DXN All rights reserved
 */
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 the accumulated length of the preceding list.
     * @return Integer
     */
    public Object emptyCase(IEmptyList host, Object acc) {
        return acc;
    }


    /**
     * Passes the accumulated length (1) to the rest and
     * asks for help to
     * compute the length.
     * @param host a non-empty list.
     * @param acc the accumulated of the preceding list.
     * @return Integer
     */
    public Object nonEmptyCase(INEList host, Object acc) {
        int accLen = 1 + ((Integer)acc).intValue();
        return host.getRest().execute(this, new Integer(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.  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.  

Aside: Once cannot claim to have understood functional programming in Scheme without knowing how to effectively use lambda expressions and higher order functions.  Similarly, one cannot claim to have understood object-oriented programming in Java without knowing how to effectively use anonymous inner classes.

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
Created with JBuilder
package listFW.visitor;

import listFW.*;

/**
 * Computes the length of a list using anonymous inner class.
 * @author D. X. Nguyen
 * @since Copyright 2002 - DXN All rights reserved
 */
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 annonymous 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;
            }

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

In the above, note how 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 (lamdba) 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 (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.  Understanding closure and knowing how to use it as inner class is crucial in writing powerful and flexible OO programs.

 


dxnguyen@cs.rice.edu
Copyright 2002, Dung X. Nguyen - All rights reserved.