Home

Hacking the java compiler: using anonymous subclasses as closures

Posted by Simon on July 10, 2008 at 11:17 PM

Categories: tech, code, java

UPDATE: new more comprehensive post on this subject: Closures with return values in Java.

In Java, closures/first-order functions are not a language feature. However, as everyone knows, you can effectively get a first-order function by using an anonymous subclass instead. Something like this:


class MyClosure {
void run() {} // override this
}
void doSomethingClosureLike() {
MyClosure closure = new MyClosure() { void run() { System.out.println("We're inside a closure!"); }};
runTheClosure(closure);
}
void runTheClosure(MyClosure closure) {
closure.run();
}
// will print We're inside a closure!

Anyway, it's simple enough, you pass the class instead of the function and there's a little extra verbage but it works!

Also you get closure-like functionality, because inside run() you can access variables from outwhere where you created it. E.g.:


void doSomethingCooler() {
final String myString = "Foo!";
MyClosure closure = new MyClosure() { void run() { System.out.println("The string is: " + myString); }};
runTheClosure(closure);
}
// will print The string is: Foo!

You can also access global variables that change over time, and the closure will use whatever is the current value WHEN THE CLOSURE RUNS.

There's just one small annoying thing, which is this particularly annoying compiler message:

local variable (WHATEVER) is accessed from within inner class; needs to be declared final

If you were do change myString to not be final, you'd get that error. Bummer. You could make myString a global variable and that would work, but that's stupid. There is a better way. Try this: UPDATE: This doesn't work, see new version at the bottom, thanks commenter the.d-stro.

void doSomethingCooler() {
String myString = "Foo!";
final String myStringFinal = myString;
myString.concat(" Bar!");
  MyClosure closure = new MyClosure() { void run() { System.out.println("The string is:" + myStringFinal); }};
 runTheClosure(closure);
}
// will print Foo! Bar!

Now you can even change myString after you assign myStringFinal, because Java, although they say it doesn't use pointers, really does use pointers. I.e. it passes by reference. So, myStringFinal is actually just a reference to myString, and keeps pointing to it even when you change the contents of myString.

You can CHANGE it (like using concat()) but you CAN'T reassign it. That will break the pointers. It makes sense if you think about it—myString will have a new memory address, and myStringFinal will still be pointing to the old memory address (and the old string value). So, this won't work:

myString = "won't work"; // breaks myStringFinal

You can use this technique with any object (but not primitives like int).

UPDATE

The last source block is wrong because java Strings are immutable. Here's an example that will work as advertised:


void doSomethingCoolest() {
StringBuffer myString = new StringBuffer("Foo!");
final StringBuffer myStringFinal = myString;
myString.append(" Bar!");
MyClosure closure = new MyClosure() { void run() { System.out.println("The string is: " + myStringFinal); }};
runTheClosure(closure);
}
// will print The string is: Foo! Bar!

Comments

There are 5 comments on this post. Post yours →

the.d-stro

Unfortunately, you're very wrong on this point: Strings in Java are immutable, meaning you can't change the value of a String object without making it point to a different String object.

No String methods actually change the value stored in the String object. myString.concat(" Bar!") will not alter myString; in this case, it will return the value "Foo! Bar!", and that return value is ignored.

Did you actually try running this code before you posted it??

Fortunately, I found the rest of this article helpful and informative.

simon

Haha, obviously not.

You're right, it doesn't work with strings. I was using another kind of (mutable) object and just assumed that the example would transpose to strings.

This will work the way I predicted:

void doSomethingCoolest() {
  StringBuffer myString = new StringBuffer("Foo!");
  final StringBuffer myStringFinal = myString;
  myString.append(" Bar!");
  MyClosure closure = new MyClosure() { void run() { System.out.println("The string is: " + myStringFinal); }};
  runTheClosure(closure);
}
// will print The string is: Foo! Bar!

Sometetimes closureas are too complicated in my opinion - sorry.

simon

I just implemented a system which could not have been nicely done in any way but using closures in java... hopefully I'll find time to post it later.

Andrew

Actually, the append works with final object itself:

myStringFinal.append("Now the final object changed.");

System.out.println(myStringFinal);

Post a comment

Required fields in bold.

 

Browse Old Articles

Categories:

Popular posts:

Subscribe to:

Blogroll: