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 (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 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.
Some important points to remember about closures and inner classes:
- An object's closure is defined at the time of its creation.
- An object "remembers" its closure for its entire lifetime.
- An inner object's closure includes any local variables that are declared as final, plus all fields of the enclosing object, including private ones.
- Every time a factory method is run, where a new anonymous object is created, a new closure for that object is created. This is because the local variables could change between calls.
- Closures enable decoupled communication because an inner class can communicate with its outer class through its closure.
One of the most important ways in which we will use anonymous inner classes it to take advantage of their closure properties. Anonymous inner classes are the only objects in Java that can be instantiated in such a manner that the variables in their environments (closures) can be dynamically defined. That is, since an anonymous inner class can reference a local variable (that is declared final) and since local variables are created every time a method is called, then every the anonymous inner class object created has a different set of dynamically created variables that it is referencing. This means that we can make unique objects with unique behaviors at run time.
Factories are a natural partner with anonymous inner classes. With a factory that returns anonymous inner classes, we can instantiate unique objects with unique behaviors. If the factory method takes in parameters, there local variables can be used to alter the resultant object's behavior, a process called "currying" (named after the famous mathematician/computer scientist Haskell Curry). The objects made by the factory are then sent off to various odd and sundry different parts of our OO system but all the while retaining their closures, which were determined at the time of their instantiation. Thus they retain the ability to communicate back to the factory that made them even though they are being used in another part of the system that knows nothing about the factory. We like to call these "spy objects" because they act like spies from the factory. This gives us powerful communications even though the system is decoupled.
This is the last piece of our abstraction puzzle! We have
- Abstract Structure - abstract classes, interfaces
- Abstract Behavior - abstract methods, strategies, visitors.
- Abstract Construction - factories
- Abstract Environments - anonymous inner classes, closures.