Tuesday, June 17, 2008

Java 5 Function Object Awesomeness

You don't see a lot of blog posts about how great function objects are in Java. It's pretty popular to hate on Java in this area. You're not really a Java programmer unless you write a blog post about how crummy they are, right? Whatever. I like Java. I also like function objects. And enum types too. Martin Fowler's Type Safe Enum in Refactoring was one of my favorite chapters. It seemed so clever, and I got a warm feeling every time I wrote one. And now the language supports it with the enum type. Welcome to 2004!

The first attempt at a function object is usually a Strategy Pattern. And you litter your code with a bunch of anonymous inner classes (ugh, the syntax is kinda cumbersome) and you declare an interface somewhere (ugh, again). This is the high ceremony of Java the critics talk about. So it might make sense to need a function object attached to an enum. Let's say you have an enum type representing a "healthy snack"; each snack can be eaten (a public method), and each snack needs it's own preparation code (hidden implementation).

My default implementation used to be to create an enum with a private inner interface, attach an instance of that interface to each enum element, and create the function object as an anonymous class instantiated in the definition:

public enum HealthySnack {
APPLE(new PrepareStrategy() {
public void perform() {
System.out.println("slice...");
}
}),
ORANGE(new PrepareStrategy() {
public void perform() {
System.out.println("peel...");
}
}),
CARROT(new PrepareStrategy() {
public void perform() {
System.out.println("skin...");
}
});

private final PrepareStrategy preparation;

private HealthySnack(PrepareStrategy preparation) {
this.preparation = preparation;
}

public void eat() {
preparation.perform();
System.out.println("eating...");
}
private interface PrepareStrategy {
void perform();
}
}

So a HealthySnack has an eat( ) method, which calls the prepare( ) step unique to each element. An apple must be sliced, an orange must be peeled, etc. Easy. A little verbose. High ceremony? Yes... but how can it be avoided? I wouldn't think this code is too abstruse.

Here's the trick I learned from Effective Java 2nd edition. What's to stop you from declaring an abstract method in the enum? And then have each element implement the method in the declaration. Nothing is stopping you, it turns out. I had no idea this was even legal, but check out the new code:

public enum HealthySnack {
APPLE { protected void prepare() {
System.out.println("slice...");
}},

ORANGE { protected void prepare() {
System.out.println("peel...");
}},

CARROT { protected void prepare() {
System.out.println("skin...");
}};

protected abstract void prepare();

public void eat() {
prepare();
System.out.println("eating...");
}
}

Much better. You get to throw out the private interface declaration. You can throw out the strategy instance field, too. And the element definition contains the overridden method but not the anonymous class declaration. The essence of varying preparation per element is revealed much better by declaring an abstract method instead of a private interface.

<caveman_voice>Book good! Book useful!</caveman_voice>

Now to go rip out all the needless cruft I've been producing the last few years.

2 comments:

Robert Fischer said...

That's a pretty cute stunt. I've smuggled data through enums before, but I've never thought about smuggling abstract methods. Like you said -- it doesn't even look like Java at that point!

And function types really aren't that bad in Java. They're chatty, but that's why God invented IDEs. A reasonable macro and you'll be tearing them out pretty fast.

Saager Mhatre said...

So, I guess you didn't catch this waaaay back when Tiger was reeleased, huh?