Posted by tball
on August 25, 2006 at 2:29 PM PDT
Back in the pre-JDK 1.1 days delegates, or bound method references, were considered and rejected by the Java language designers in favor of inner classes. What has changed so much that these reasons no longer apply?
The recent blogs on closures have left me with a real sense of dÃ©jÃ vu . A few months after the JDK 1.0 released (over ten years ago), Microsoft proposed that the Java language be extended with type-safe method references, which they called delegates . The AWT team I was a part of liked this proposal and pushed for implementing it, as it fit well with the new GUI event model we were designing to support Java Beans. The Java language team shot the proposal down, however, responding publicly with the About Microsoft's "Delegates" white paper, which described how everything you might want from a delegate can be better handled with inner classes. Microsoft responded with The Truth about Delegates , and the two companies eventually went their separate ways, language-wise, with C# supporting delegates, and adding anonymous delegates (which look a lot like what's described in the Java closures proposal ) in C# 2.0.
When the Java language team shot down the delegates proposal, the AWT team grumbled quite a bit since delegate-like equivalents are frequently used for event-driven application development (which is undoubtedly why Microsoft's GUI team drove the proposal from their side). But then an interesting thing happened: due to this constraint (no delegates), our event design improved significantly. As the old saying goes, sometimes unanswered prayers are blessings. If we had delegates back then, the AWT might be (even) slower and (even) harder to use than it is today.
I don't mean to suggest closures are evil (I live close enough to the old Xerox Parc site that some Smalltalk fanatics might pass messages to me wrapped around a rock!), but language features do impact API and performance work. In this case, the AWT has a design constraint that was better addressed without using delegates: lots of different event methods that need to be dispatched to interested components very quickly. This may be hard to believe, but in Mustang the AWT and Swing define over ninety component events, while toolkit performance must keep improving even as the complexity of desktop applications increases.
In 1.0, the AWT routed events up component hierarchy until a component "consumed" the event, which doesn't scale well to even small applications due to the number of events fired during normal usage. What made this worse was that frequently fired events such as mouse movement or scrolling events weren't interesting to most applications, so their dispatch took the longest since no component consumed them. Think I'm exaggerating? During the early Swing days, Hans Muller's favorite performance test was to open a scrolling window with a lot of text or a big graphic, click-and-hold on the vertical scrollbar, and wiggle it up and down quickly to check for lags. Not the most elegant test, but it quickly and reliably uncovered performance issues because thousands of events get fired during that wiggling, and if they aren't dispatched and handled quickly, then the whole GUI bogs down.
But what does this have to do with closures/delegates/better design? I'm getting to that. What's the fastest performance Big O number? O(log N)? O(1)? Try zero. The fastest code is the code that isn't executed. That's why in 1.1 the AWT moved to a publish-and-subscribe event model (the "new" event model), so that events the application didn't care about weren't dispatched. This is great for performance, but it makes the API more complex because you need a separate method for each event, thus making it harder to use. It also encourages code duplication in similar event handlers, making the application potentially less robust (fixing a bug in multiple places is less reliable than just one).
We defined groups of GUI events to address the API complexity issue, by declaring a shared data instance (Event class) and event interface (Listener) for each group. The advantage of listeners is that related event methods are in one class, each of which share a common Event class for event data. This reduced the complexity and data sharing issues to a certain extent. The problem then became that developers didn't want to write event handlers for events they weren't interested in, so Adapter classes were created so uninteresting events get no-op'd. This combination of listener interfaces with Event objects and Adapter classes balances the optimization needs of the toolkit with developer ease-of-use.
Tom, WHAT DOES THIS HAVE TO DO WITH CLOSURES/DELEGATES/BETTER DESIGN?!? Simple: multiple event Listener interfaces and Adapter classes aren't possible with delegates or closures, as each event requires a separate delegate. If delegates had been added to 1.1, the AWT team would have used them instead of the current design (since delegates are well-suited for event dispatch systems), and AWT development would now be (even) harder while performance would be (even) slower.
I'm therefore on the fence regarding adding closures to Java. While I like functional programming and agree that certain problems can be coded more succiently using closures, I'm worried about how overuse of them can hurt performance and API design. Read the whitepapers linked above, and then tell me what points I missed.