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); } }
|
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.
|
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.
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 ...
}
}
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.
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.
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.
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).