Sun Microsystems recently released a white paper that derides delegates as "unnecessary", "detrimental to the language", "harmful", "complex", and "not object-oriented." We strongly disagree with Sun’s technical arguments.
Here is a brief outline of the rest of this document:
1.1 Delegates’ dynamic advantages
1.2 Delegates are simpler than inner classes for event scenarios
1.3 Delegates can be both efficient and portable
1.4 Delegates fit cleanly with existing language and VM features
1.5 Delegates result in leaner source code and executable code
1.6 Delegates do not constrain implementation options
1.7 Delegates are better for multicast
1.8 Delegates are implemented with classes, not inner classes
Sun’s white paper identifies the need for "some equivalent of method pointers" to support "pluggable" APIs:
It has been clear from the outset that the original language needed some equivalent of method pointers in order to support delegation and "pluggable" APIs. James Gosling noted this in his keynote at the first JavaOne conference.
We agree with this statement, and believe that a delegate-based event model is exactly the solution to this problem. In fact, we believe that JavaBeans’ interface-based event model precludes JavaBeans from being "pluggable" in some important scenarios.
Let’s first examine the mechanism by which JavaBean components are "plugged together" – the mechanism by which events coming from one JavaBean are wired to methods of another JavaBean. To connect two JavaBeans, the source of the events must be provided with an implementation of a listener interface. However, even if a JavaBean implements a method that is "plug compatible" with an event from another JavaBean (one JavaBean has a public method whose signature matches the event of another JavaBean), it is not possible to directly wire the two together. The target JavaBean would have to implement the event listener interface required by the source JavaBean, which is not only highly unlikely, but also restrictive in that it does not allow the target JavaBean to pick meaningful method names for the event handlers. To complete the wiring of two JavaBeans, "glue code" must be provided. Following Sun’s recommendations, this glue code takes the form of an adapter class which implements the event listener interface and forwards events to methods of the receiver. A Java compiler must compile the glue code before the connection between the two JavaBeans can be made.
Most Java development tools support automatic generation and maintenance of glue code (adapter classes), and allow creation of applications that are "pre-wired" – applications wherein the connections between JavaBeans are determined at development-time but remain static at run-time. This solution is acceptable to developers, but does not address a more dynamic type of application where JavaBeans are wired together at run-time. Given that glue code is required to connect two JavaBeans, it follows that a running application cannot dynamically wire JavaBeans together. A running application is very unlikely to have the ability to arbitrarily create and compile Java code—indeed, an application running in the "Sandbox" (such as an applet) is subject to security restrictions that specifically preclude such dynamic generation of code. Furthermore, the glue code requirement severely complicates "code-less" tools, such as Sun’s own Java Studio and Lotus’ BeanMachine, through which end users can visually create applications by wiring together JavaBeans. Such tools are currently forced to generate and compile Java code in order to produce working applications.
Now contrast the interface-based event model with the delegate-based event model provided by WFC. In the delegate-based model, an event from any component can be connected to a method of any other component as long as the signatures of the event and the method are compatible. The act of connecting the two requires no intervening adapter class, and a development tool does not have to generate and maintain additional source code. Furthermore, a running application can dynamically create such connections. For example, a running application can apply reflection and introspection to "live" components to determine their events and methods, and can freely create connections between the components in a type-safe manner. Finally, while the delegate-based event model is not only simpler to manage for traditional development tools, it is also more suitable for "code-less" tools such as Java Studio and BeanMachine, since such tools would never have to deal with code generation.
Some might shrug off the fact that beans are inherently plug-incompatible by pointing out that Java and JavaBeans are for developers rather than end users. We disagree with this reasoning. Dynamic wiring is important for enabling higher-level programming by both developers and end users. In fact, as we have pointed out, many in the industry are focusing on these problems, producing tools such as Sun’s own Java Studio and Lotus’ BeanMachine. Truly dynamic wiring would provide the following benefits:
We find it ironic that Sun appreciates the importance of these problems enough to create a product focused on these problems, but does not recognize the limitations of its own event model.
Sun claims that delegates add complexity to the language:
We believe bound method references are harmful because they detract from the simplicity of the Java programming language.
and that they add little value to offset this complexity:
[Delegates] are no more convenient than adapter objects. Although our analysis indicated that some code examples could be expressed more concisely with specialized method reference syntax, we also found that the additional notational overhead for inner classes was never more than a handful of tokens.
The truth is that delegate are simpler than inner classes for event scenarios. WFC’s delegate-based event model results in more concise, more readable, and conceptually simpler source code than the JavaBeans’ interface-based event model.
Sun seems to correlate simplicity of the language with the number of characters that a developer types. By this measure delegates clearly win, but we believe that the main advantage of delegates is that they are conceptually simpler to use for event scenarios than adapter classes. JavaBeans’ interface-based event model effectively dictates the use of adapter classes. We believe that asking developers to implement a separate adapter class in order to handle an event introduces an absurd amount of unnecessary conceptual complexity.
To illustrate this point, we present one example written two ways: the JavaBeans way and the WFC way. The example is a canonical one: implement a dialog with "OK" and "Cancel" buttons. (The code presented below is abbreviated; complete code is available for download in the following paragraph.)
Click to copy the OKCancel code sample .
The JavaBeans version, which uses inner classes to connect event handlers, is below. Note the anonymous adapter classes declared within the jbInit method. Each of these classes implements an event handler interface, and delegates to an event handler. This design pattern, in which the event plumbing is separated from the actual event handler, is typical of the code generated by JavaBean development tools, and is identical to the model advocated by Sun in the inner classes specification.
class SimpleFrame extends DecoratedFrame { Button buttonOK = new Button(); Button buttonCancel = new Button(); void buttonOK_actionPerformed(ActionEvent e) { System.out.println("OK clicked"); } void buttonCancel_actionPerformed(ActionEvent e) { System.out.println("Cancel clicked"); } void jbInit() throws Exception { buttonOK.setLabel("OK"); buttonOK.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { buttonOK_actionPerformed(e); } }); buttonCancel.setLabel("Cancel"); buttonCancel.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { buttonCancel_actionPerformed(e); } }); this.add(buttonOK, BorderLayout.NORTH); this.add(buttonCancel, BorderLayout.SOUTH); } }
The WFC version, which uses delegates to connect event handlers, is below. Note the instantiation of EventHandler delegates in the initForm method. These delegates encapsulate the actual event handlers. When the invoke method of a delegate is called, the method it encapsulates is called. For event handlers, this occurs when the event source raises the event. The code in the example was generated using Microsoft® Visual J++® 6.0. Other design patterns are possible with the delegate-based model, but we believe that developers and tools vendors will favor the approach used in the example.
class SimpleForm extends Form { Button buttonOK = new Button(); Button buttonCancel = new Button(); void buttonOK_click(Object sender, Event e) { System.out.println("Clicked OK"); } void buttonCancel_click(Object sender, Event e) { System.out.println("Clicked Cancel"); } void initForm() { buttonOK.setText("OK"); buttonOK.addOnClick(new EventHandler(this.buttonOK_click)); buttonCancel.setText("Cancel"); buttonCancel.addOnClick(new EventHandler(this.buttonCancel_click)); this.setNewControls(new Control[] {buttonCancel, buttonOK}); } }
We believe that it is self-evident that the WFC code for connecting an event handler to a button’s click event:
buttonOK.addOnClick(new EventHandler(this.buttonOK_click));
is more concise, more readable, and conceptually simpler than the corresponding JavaBeans code:
buttonOK.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { buttonOK_actionPerformed(e); } });
The readability of code that uses an interface-based event model is further reduced due to its coarse granularity. Typically, event handlers are sparse: most components have at least one handled event, but most events are unhandled. Because all methods of an interface must be implemented, JavaBeans’ interface-based event model results in adapter code for all events in an interface, even if just one of these events is handled. This unnecessary code hampers readability, and creates unnecessary runtime overhead. WFC’s delegate-based event model does not suffer from these problems. Events are handled one-by-one, so there is no readability or runtime cost for unhandled events.
Sun claims that delegates cannot be both efficient and portable:
Looking "under the hood," it is easy to understand our conclusion that any implementation of bound method references will either be inefficient or non-portable. A portable implementation of delegates that generates code capable of running on any compliant implementation of the Java platform would have to rely on the standard Java Core Reflection API.
The truth is that delegates can be both efficient and portable:
We believe that it is inappropriate to consider language and VM innovations as entirely separate matters, as Sun is doing in the section above. Considering language and virtual machine issues separately is appropriate for some features, but not for all of them. It is unfair to characterize delegates as "non-portable" because existing virtual machines do not support them. The question at hand is whether Java language and Java virtual machines should have support for delegates.
Sun’s arguments regarding implementation of delegates using reflection are irrelevant because it is not possible to correctly implement delegates using reflection. Using delegates, a developer can create a delegate that encapsulates a private method on a class. When the delegate’s invoke method is called, the delegate calls the private method. In order to make such a call, the Delegate base class needs to be able to invoke private methods on classes. Reflection could provide this capability, but it does not. Thus, the language and VM aspects of delegates cannot be discussed separately.
Sun’s implicit insistence to discuss the language and VM aspects of delegates is interesting in another way. Using Sun’s twisted logic, it is easy to conclude that any innovation that requires VM changes is non-portable. Such innovations will occur; the Java platform will not remain fixed for eternity. Sun’s circular arguments make it impossible to have a reasonable discussion about which innovations will be adopted as part of the Java platform. Sun would rather obfuscate than address the admittedly difficult issue of how the Java platform will evolve over time. Denying evolution makes planning for it impossible.
Sun claims that delegates add complexity and do not fit well with existing Java features:
Delegates add complexity. The type system must be extended with an entirely new kind of type, the bound method reference. New language rules are then required for matching expressions of the form `x.f' to method reference constructors, most notably for overloaded or static methods.
The truth is that delegates fit cleanly with existing language and VM features. The steps involved in resolving a delegate instantiation expression are precisely the same as for resolving an expression involving a method call. It is true that Java’s rules for method resolution result in complex compiler and VM logic, but delegates merely leverage this logic. They do not complicate it further. Delegates define no new language rules related to method resolution.
Sun claims that:
Delegates dilute the investment in VM technologies because VMs are required to handle additional and disparate types of references and method linkage efficiently.
Delegates result in loss of object orientation. Because of their special role and supporting syntax, bound method references are "second-class citizens" among other reference types. For example, while delegate types in Visual J++ are technically classes, they cannot extend classes chosen by the programmer, nor implement arbitrary interfaces.
Delegates are limited in expressiveness. Although bound method references appear to the Microsoft VM as reference types, they are no more expressive than function pointers. For example, they cannot implement groups of operations or contain state, and so cannot be used with Java class library iteration interfaces. We wanted the Java language to be class-based, in which classes do all the work, rather than supplying function pointers as an alternate and redundant way to perform the same delegation tasks.
The truth is that delegates fit well with the existing language, are object-oriented, and are appropriately expressive.
Sun confuses the issues by implicitly comparing delegates with inner classes. We believe that it is interesting to compare a delegate-based event model with an interface-based event model that uses inner classes, but that it is not interesting to compare delegates with inner classes. We do not claim that delegates should replace inner classes, or that inner classes should not exist. We do claim that delegates are generally useful, and that WFC’s delegate-based event model is superior to JavaBeans’ interface-based event model.
Sun shrugs off the advantages of a delegate-based event model as compared to JavaBeans’ interface-based event model.
Delegates are no more convenient than adapter objects. Although our analysis indicated that some code examples could be expressed more concisely with a specialized method reference syntax, we also found that the additional notational overhead for inner classes was never more than a handful of tokens.
The truth is that delegates result in leaner source code and executable code for event scenarios. To make the comparison between WFC’s delegate-based event model and JavaBeans’ interface-based event model concrete rather than theoretical, we created a simple text editor using three different eventing mechanisms:
In each case, the text editor consists of a window that contains a text box. The window has a menu bar that contains the same menu items as the default Windows text editor (Notepad). Each menu item is connected to an event handler. In our simple example, each event handler simply displays a message notifying the user that the feature has not yet been implemented. We chose Notepad as an example primarily because of its size – it is both small enough to be easily understood, and big enough to make extrapolation to a larger example meaningful.
Click here to copy the TruthNotepad sample .
The data below shows the tremendous costs of adapter classes, and that these costs are incurred independent of the mechanism used to implement the adapter classes.
Mechanism | Number of classes | Size of executable files (bytes) | Size of source files (bytes) |
Adapter classes (top-level) | 22 | 19,000 | 14,742 |
Adapter classes (inner) | 22 | 20,806 | 10,842 |
Delegates | 1 | 5,462 | 9,279 |
The results are striking:
Sun will likely argue that more advanced techniques can be used to lower the number of adapter classes that are needed. We too have been down this path, and recognize that these other techniques have their own drawbacks (such as slower event dispatching), and minimize the problems rather than solve them. Delegates solve the problems by doing away with the need for adapter classes in the first place.
Sun may also argue that some magical VM innovation is just around the corner, and that this innovation will solve the executable size problems. Each adapter class in the examples above is approximately 600 bytes, which is quite large given how many of them there are. We believe that substantial reduction in this overhead is theoretically possible, but we believe that classes will continue to be expensive for quite some time, both due to the nature of the problem and because reducing the size of class files requires changes to all virtual machines. We believe that a delegate-based event model will continue to have size advantages for many years.
Sun claims that a delegate-based event model places undue constraints on the structure of an application:
Method references require the programmer to flatten the code of the application into a single class and choose a new identifier for each action method, whereas the use of adapter objects requires the programmer to nest the action method in a subsidiary class and choose a new identifier for the class name.
The truth is that delegates do not constrain implementation options. WFC’s delegate-based event model allows event handlers to be placed anywhere; the interface-based model forces use of an adapter class.
With the delegate-based event model, developers have a great deal of flexibility. Many developers find it convenient to implement event handlers for a component using private methods on the component’s container. For example, the form below shows a form with two buttons and a click handler for each button.
public class MainForm extends Form { Button buttonOK = new Button(); Button buttonCancel = new Button(); public MainForm() { buttonOK.addOnClick(new EventHandler(this.buttonOK_click)); buttonCancel.addOnClick(new EventHandler(this.buttonCancel_click)); } private void buttonOK_click(Object sender, Event e) { // Your code here } private void buttonCancel_click(Object sender, Event e) { // Your code here } }
The event model does not dictate the use of this design pattern. Any method on any class may be used, so long as the signature of this method matches the signature of the event being raised. Note that event handlers may be private methods, so adapter classes are not required to hide event handlers from the outside world.
Ironically, it is the interface-based model that constrains the structure of code. Event handlers must appear as public methods on a class that implements the appropriate listener interface. Since a single class cannot implement an interface more than once, this model essentially requires that event handlers appear as members on adapter classes.
Sun argues that inner classes are superior to delegates for use in multicast event scenarios:
When implementing new event types in the Swing toolkit, a component author may use the EventListenerList class to manage lists of event handlers. Code using EventListenerList is slightly more verbose than the corresponding code relying on the Microsoft® Visual J++® compiler support for multicasting. On the other hand, coding multicast in the standard Java language is more straightforward and less "magical." This approach is also more easily modified to create variations of the basic event notification procedure, such as running boolean-valued event handlers until one returns true, or adding up the results produced by integer-valued event handlers.
Moreover, the cost model for multicasting in a program written in the unadulterated Java language is easier to understand and (if necessary) modify. For example, each additional listener in a Swing component occupies exactly two additional words, and a component with no listeners uses no storage for the absent listeners. Instead of a one-size-fits-all multicast mechanism, Swing designers and extenders are free to use whichever data structure best fits the application.
The truth is that delegates are superior to inner classes for multicast event scenarios. Taking Sun’s points one-by-one:
Sun claims that the costs of multicasting are more easily managed for an interface-based event model than for a delegate-based one. This is not true. Developers can use any data structure they wish to manage a list of connected delegates. The Delegate class provides automatic support for this, but there is no requirement that this support be employed. The event models are equivalent in this area. In either case, a multicast-enabled component can use whatever data structure it pleases to manage a set of connected listeners.
Sun's inaccurate technical analysis has not been restricted to white papers. In an article located at http://www.developer.com/news/stories/040798_gosling.html in the online journal developer.com (located at http://www.developer.com/
), Sun's James Gosling comments:
"So [Hejlsberg] basically implemented kind of a funky version of method pointers, from eons ago," Gosling said, returning to his key design objection to the WFCs. "Although under the covers, he actually does it on top of inner classes. Now this looks really silly."
The truth is that delegates are implemented using classes, not inner classes. Gosling’s claim that delegates are implemented on top of inner classes is plainly wrong.