The Truth About Delegates

Sun Microsystems recently released a white paper that derides delegates as "unnecessary", "detrimental to thelanguage", "harmful", "complex", and "notobject-oriented." We strongly disagree with Sun’s technical arguments.

1.1 Delegates’ dynamic advantages

Sun’s white paper identifies the need for "some equivalent of methodpointers" 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 exactlythe solution to this problem. In fact, we believe that JavaBeans’ interface-basedevent model precludes JavaBeans from being "pluggable" in some importantscenarios.

Let’s first examine the mechanism by which JavaBean components are "pluggedtogether" – the mechanism by which events coming from one JavaBean are wired tomethods of another JavaBean. To connect two JavaBeans, the source of the events must beprovided with an implementation of a listener interface. However, even if a JavaBeanimplements a method that is "plug compatible" with an event from anotherJavaBean (one JavaBean has a public method whose signature matches the event of anotherJavaBean), it is not possible to directly wire the two together. The target JavaBean wouldhave to implement the event listener interface required by the source JavaBean, which isnot only highly unlikely, but also restrictive in that it does not allow the targetJavaBean to pick meaningful method names for the event handlers. To complete the wiring oftwo JavaBeans, "glue code" must be provided. Following Sun’srecommendations, this glue code takes the form of an adapter class which implements theevent listener interface and forwards events to methods of the receiver. A Java compilermust 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 atdevelopment-time but remain static at run-time. This solution is acceptable todevelopers, but does not address a more dynamic type of application where JavaBeansare 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. Arunning application is very unlikely to have the ability to arbitrarily create and compileJava code—indeed, an application running in the "Sandbox" (such as anapplet) is subject to security restrictions that specifically preclude such dynamicgeneration 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 togetherJavaBeans. Such tools are currently forced to generate and compile Java code in order toproduce working applications.

Now contrast the interface-based event model with the delegate-based event modelprovided by WFC. In the delegate-based model, an event from any component can be connectedto a method of any other component as long as the signatures of the event and the methodare compatible. The act of connecting the two requires no intervening adapter class, and adevelopment tool does not have to generate and maintain additional source code.Furthermore, a running application can dynamically create such connections. Forexample, a running application can apply reflection and introspection to "live"components to determine their events and methods, and can freely create connectionsbetween the components in a type-safe manner. Finally, while the delegate-based eventmodel is not only simpler to manage for traditional development tools, it is also moresuitable for "code-less" tools such as Java Studio and BeanMachine, since suchtools would never have to deal with code generation.

Some might shrug off the fact that beans are inherently plug-incompatible by pointingout that Java and JavaBeans are for developers rather than end users. We disagree withthis reasoning. Dynamic wiring is important for enabling higher-level programming by bothdevelopers and end users. In fact, as we have pointed out, many in the industry arefocusing on these problems, producing tools such as Sun’s own Java Studio andLotus’ BeanMachine. Truly dynamic wiring would provide the following benefits:

  • Reduce the working set size of tools. Tools focused entirely on end users would not need a compiler at all.
  • Reduce the complexity and size of solutions. Generated code is great for developers because it provides complete flexibility. The utility of such flexibility is substantially or entirely reduced for end users, who either primarily or entirely use designers ratherthan code. Higher-level tools can reduce complexity and size by reducing or eliminating generated code, but this is impossible with JavaBeans because all JavaBeans require glue code.
  • Reduce the runtime redistributable required for solutions that are customizable by end users. Again, no compiler is needed.

We find it ironic that Sun appreciates the importance of these problems enough tocreate a product focused on these problems, but does not recognize the limitations of itsown event model.

1.2 Delegates are simpler than inner classes for event scenarios

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, andconceptually simpler source code than the JavaBeans’ interface-based event model.

Sun seems to correlate simplicity of the language with the number of characters that adeveloper types. By this measure delegates clearly win, but we believe that the mainadvantage of delegates is that they are conceptually simpler to use for event scenariosthan adapter classes. JavaBeans’ interface-based event model effectively dictates theuse of adapter classes. We believe that asking developers to implement a separate adapterclass in order to handle an event introduces an absurd amount of unnecessary conceptualcomplexity.

To illustrate this point, we present one example written two ways: the JavaBeans wayand the WFC way. The example is a canonical one: implement a dialog with "OK"and "Cancel" buttons. (The code presented below is abbreviated; click here to download the complete examples.)

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 eventhandler interface, and delegates to an event handler. This design pattern, in which theevent plumbing is separated from the actual event handler, is typical of the codegenerated by JavaBean development tools, and is identical to the model advocated by Sun inthe 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 theinstantiation of EventHandler delegates in the initForm method. These delegates encapsulate the actual event handlers. When theinvoke method of a delegate is called,the method it encapsulates is called. For event handlers, this occurs when the eventsource 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 thatdevelopers 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 toa button’s clickevent:

buttonOK.addOnClick(new EventHandler(this.buttonOK_click));

is more concise, more readable, and conceptually simpler than the correspondingJavaBeans 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 dueto its coarse granularity. Typically, event handlers are sparse: most components have atleast one handled event, but most events are unhandled. Because all methods of aninterface must be implemented, JavaBeans’ interface-based event model results inadapter code for all events in an interface, even if just one of theseevents is handled. This unnecessary code hampers readability, and creates unnecessaryruntime overhead. WFC’s delegate-based event model does not suffer from theseproblems. Events are handled one-by-one, so there is no readability or runtime cost forunhandled events.

1.3 Delegates can be both efficient and portable

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:

  • There is no technical barrier to adding delegate support to virtual machines besides the Microsoft VM.
  • Delegates are efficient. Invoking a delegate is as fast as invoking a virtual method.

We believe that it is inappropriate to consider language and VM innovations as entirelyseparate matters, as Sun is doing in the section above. Considering language and virtualmachine issues separately is appropriate for some features, but not for all of them. It isunfair to characterize delegates as "non-portable" because existing virtualmachines do not support them. The question at hand is whether Java language andJava virtual machines should have support for delegates.

Sun’s arguments regarding implementation of delegates using reflection areirrelevant 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 aclass. When the delegate’s invoke method is called, the delegate calls the private method. In order to make such acall, 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 VMaspects of delegates cannot be discussed separately.

Sun’s implicit insistence to discuss the language and VM aspects of delegates isinteresting in another way. Using Sun’s twisted logic, it is easy to conclude that anyinnovation that requires VM changes is non-portable. Such innovations will occur;the Java platform will not remain fixed for eternity. Sun’s circular arguments makeit impossible to have a reasonable discussion about which innovations will be adopted aspart of the Java platform. Sun would rather obfuscate than address the admittedlydifficult issue of how the Java platform will evolve over time. Denying evolution makesplanning for it impossible.

1.4 Delegates fit cleanly with existinglanguage and VM features

Sun claims that delegates add complexity and do not fit well with existing Javafeatures:

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. Thesteps involved in resolving a delegate instantiation expression are precisely the same asfor resolving an expression involving a method call. It is true that Java’s rules formethod resolution result in complex compiler and VM logic, but delegates merely leveragethis logic. They do not complicate it further. Delegates define no new language rulesrelated 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 Java 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.

  • Delegates fit well with the existing language. As we noted above, the rules that delegates employ for method resolution are precisely the same as for normal method resolution.
  • Delegates are object-oriented. A delegate declaration results in a class that derives from Delegate; a delegate instance is an instance of a class. We have no idea what Sun means by "disparate types of references."
  • Delegates are not "second-class citizens" for reasons of expressiveness. There is nothing "second-class" about the expressiveness of delegates, just as there is nothing second-class about the expressiveness of other primitive types in Java, such as String and Array. Is the Java language poorer because it is not possible for Java developers to create array classes that cannot extend classes chosen by the programmer or implement arbitrary interfaces? Of course not. And the same logic applies equally well to delegates.

Sun confuses the issues by implicitly comparing delegates with inner classes. Webelieve that it is interesting to compare a delegate-based event model with aninterface-based event model that uses inner classes, but that it is not interestingto compare delegates with inner classes. We do not claim that delegates shouldreplace inner classes, or that inner classes should not exist. We do claim thatdelegates are generally useful, and that WFC’s delegate-based event model is superiorto JavaBeans’ interface-based event model.

1.5 Delegates result in leaner sourcecode and executable code

Sun shrugs off the advantages of a delegate-based event model as compared toJavaBeans’ 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 eventscenarios. To make the comparison between WFC’s delegate-based event model andJavaBeans’ interface-based event model concrete rather than theoretical, we created asimple text editor using three different eventing mechanisms:

  • WFC’s delegate-based event model.
  • JavaBeans’ event model, using adapter classes implemented with top-level classes. For this example, we employed the design pattern recommended in Sun’s JavaBeans specification, which was written before inner classes were added to Java. We used the formdesigner in Borland JBuilder to generate the code.
  • JavaBeans’ event model, using adapter classes implemented with inner classes. For this example, we employed the event handling design pattern recommended in Sun’s Inner Classes specification.

In each case, the text editor consists of a window that contains a text box. The windowhas a menu bar that contains the same menu items as the default Microsoft 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 download the complete examples.)

The data below shows the tremendous costs of adapter classes, and that these costs areincurred 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:

  • Each of the interface-based examples has 22 classes; the delegate-based example has one class. The reason for this astounding difference is that the interface-based event model requires a unique adapter class for each event source
  • The interface-based examples are four times larger on-disk than the delegate-based example. Bandwidth is too important a commodity to spend on unnecessary adapter classes.
  • The interface-based examples result in 17%-59% more source code than the delegate-based example. Besides being more concise, the delegate-based code is also more readable. This point is subjective, so we suggest that readers judge for themselves by examining the code for our examples.

Sun will likely argue that more advanced techniques can be used to lower the number ofadapter classes that are needed. We too have been down this path, and recognize that theseother techniques have their own drawbacks (such as slower event dispatching), and minimizethe problems rather than solve them. Delegates solve the problems by doing away with theneed for adapter classes in the first place.

Sun may also argue that some magical VM innovation is just around the corner, and thatthis innovation will solve the executable size problems. Each adapter class in theexamples above is approximately 600 bytes, which is quite large given how many of themthere are. We believe that substantial reduction in this overhead is theoreticallypossible, 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 filesrequires changes to all virtual machines. We believe that a delegate-based event modelwill continue to have size advantages for many years.

1.6 Delegates do not constrain implementation options

Sun claims that a delegate-based event model places undue constraints on the structureof 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’sdelegate-based event model allows event handlers to be placed anywhere; theinterface-based model forces use of an adapter class.

With the delegate-based event model, developers have a great deal of flexibility. Manydevelopers find it convenient to implement event handlers for a component usingprivate methods on the component’s container. For example, the form below shows aform 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. Anymethod on any class may be used, so long as the signature of this method matches thesignature of the event being raised. Note that event handlers may be private methods, soadapter 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 appropriatelistener interface. Since a single class cannot implement an interface more than once,this model essentially requires that event handlers appear as members on adapterclasses.

1.7 Delegates are better for multicast

Sun argues that inner classes are superior to delegates for use in multicast eventscenarios:

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 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 eventscenarios. Taking Sun’s points one-by-one:

  • Sun admits that the use of delegates results in more concise code, but minimizes the degree to which the two models differ. WFC’s event model provides automatic multicast; JavaBeans’ event model does not provide this capability.
  • Sun claims that interface-based multicasting is "more straightforward" and "less magical". There is nothing magical about the automatic multi-casting provided by delegates. The behavior provided in the delegate base classes is a well-documented part of these classes. If Sun believes that any behavior that isencapsulated within a component is "magical", this reflects an extremely warped view of component-based development.
  • Sun claims that the interface-based event model results in code that is more easily modifiable to custom multicast rules, ignoring the drawbacks of this model: that the "boilerplate code approach" is error-prone, and puts unnecessary burden on component developers. Delegates provide uniform multicast behavior by default, while alsoenabling custom multicast behaviors. Developers can multicast in a standard manner with a single line of code, or multicast in a custom manner by manually iterating over the invocation list.
  • 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.

1.8 Delegates are implemented withclasses, not inner classes

Sun’s inaccurate technical analysis has not been restricted to white papers. In andeveloper.com article, 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 plainlywrong.