Closures & Callbacks
A closure is a callable object that retains information from the scope in which it was created. From this definition, you can see that an inner class is an object-oriented closure, because it doesn’t just contain each piece of information from the outer class object (“the scope in which it was created”), but it automatically holds a reference back to the whole outer class object, where it has permission to manipulate all the members, even private ones.
One of the most compelling arguments made to include some kind of pointer mechanism in Java was to allow callbacks. With a callback, some other object is given a piece of information that allows it to call back into the originating object at some later point. This is a very powerful concept, as you will see later in the book. If a callback is implemented using a pointer, however, you must rely on the programmer to behave and not misuse the pointer. As you’ve seen by now, Java tends to be more careful than that, so pointers were not included in the language.
The closure provided by the inner class is a perfect solution—more flexible and far safer than a pointer. Here’s an example:
//: c08:Callbacks.java
// Using inner classes for callbacks
import com.bruceeckel.simpletest.*;
interface Incrementable {
void increment();
}
// Very simple to just implement the interface:
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
void increment() {
System.out.println("Other operation");
}
static void f(MyIncrement mi) { mi.increment(); }
}
// If your class must implement increment() in
// some other way, you must use an inner class:
class Callee2 extends MyIncrement {
private int i = 0;
private void incr() {
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
public void increment() { incr(); }
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) { callbackReference = cbh; }
void go() { callbackReference.increment(); }
}
public class Callbacks {
private static Test monitor = new Test();
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
monitor.expect(new String[] {
"Other operation",
"1",
"2",
"1",
"2"
});
}
} ///:~
This example also provides a further distinction between implementing an interface in an outer class versus doing so in an inner class. Callee1 is clearly the simpler solution in terms of the code. Callee2 inherits from MyIncrement, which already has a different increment( ) method that does something unrelated to the one expected by the Incrementable interface. When MyIncrement is inherited into Callee2, increment( ) can’t be overridden for use by Incrementable, so you’re forced to provide a separate implementation using an inner class. Also note that when you create an inner class, you do not add to or modify the interface of the outer class.
Notice that everything except getCallbackReference( ) in Callee2 is private. To allow any connection to the outside world, the interface Incrementable is essential. Here you can see how interfaces allow for a complete separation of interface from implementation.
The inner class Closure implements Incrementable to provide a hook back into Callee2—but a safe hook. Whoever gets the Incrementable reference can, of course, only call increment( ) and has no other abilities (unlike a pointer, which would allow you to run wild).
Caller takes an Incrementable reference in its constructor (although the capturing of the callback reference could happen at any time) and then, sometime later, uses the reference to “call back” into the Callee class.
The value of the callback is in its flexibility; you can dynamically decide what methods will be called at run time. The benefit of this will become more evident in Chapter 14, where callbacks are used everywhere to implement GUI functionality.