Sun Java Solaris Communities My SDN Account Join SDN
 
White Paper

Sidebar: Implementing Delegates using Reflection

 

Main Article

Implementing Delegates using Reflection

The following is a simple example of the use of the Visual J++ 6.0 delegation mechanism, taken from the Microsoft technical article Delegates in Visual J++ 6.0. We have simply added the minimal wrapper required to turn the code fragment appearing there into a compilable program:

delegate long IntOp(int a, int b);

public class Example {
  long add(int a, int b) {
    return a + b;
  }
  public void doIt() {
    IntOp op = new IntOp(this.add);
    long result = op.invoke(2, 3);
  }
}

Using reflection, we might render this example as follows, in standard Java language code:

import java.lang.reflect.*;

class DelegateCreationException extends RuntimeException {};

class DelegateInvocationException extends RuntimeException {};

class IntOp {
  Method m;
  Object self;
  IntOp(Object obj, String name) {
    self = obj; 
    Class[] argTypes = { int.class, int.class };
    try {
      m = obj.getClass().getDeclaredMethod(name, argTypes);
    } catch (NoSuchMethodException e) {
      throw new DelegateCreationException();
    }
  }
  long invoke(int a, int b) {
    Object[] args = new Object[2];
    args[0] = new Integer(a);
    args[1] = new Integer(b);
    try {
      Long result = (Long)m.invoke(self, args);
      return result.longValue();
    } catch (IllegalAccessException e) {
      throw new DelegateInvocationException();
    } catch (InvocationTargetException e) {
      Throwable t = e.getTargetException();
      if (t instanceof Error)
        throw (Error)t;
      if (t instanceof RuntimeException)
        throw (RuntimeException)t;
      throw new DelegateInvocationException();
    }
  }
}

public class Example {
  long add(int a, int b) {
    return a + b;
  }
  public void doIt() {
    IntOp op = new IntOp(this, "add");
    long result = op.invoke(2, 3);
  }
}

Although this definition is rather long-winded, we see that the use of the delegate class IntOp is straightforward, and syntactically resembles the previous version using the special syntax. A compiler could use a similar transformation to implement the delegate declaration syntax in a manner compatible with the standard Java platform. (Note: We have glossed over certain issues of access-control, which would appear if the method add were made private, or if the delegate were invoked outside the current package. Addressing this problem would require additional complexity, but is possible by means of the AccessibleObject class found in newer releases of the Java class libraries.)

Much of the complexity in this example is due to the fact that we map checked exceptions that are not expected to occur to a pair of new unchecked exceptions, DelegateCreationException and DelegateInvocationException. In practice, a compiler would exploit the observation that these exceptions could never occur in the transformed code if the original code passed compile-time checks. The bytecode generated for IntOp would then correspond to the following:

class IntOp {
  Method m;
  Object self;
  IntOp(Object obj, String name) {
    self = obj; 
    Class[] argTypes = { int.class, int.class };
    m = obj.getClass().getDeclaredMethod(name, argTypes);
  }
  long invoke(int a, int b) {
    Object[] args = new Object[2];
    args[0] = new Integer(a);
    args[1] = new Integer(b);
    try {
      Long result = (Long)m.invoke(self, args);
      return result.longValue();
    } catch (InvocationTargetException e) {
      throw e.getTargetException();
    }
  }
}

This code will not pass compile-time checks, but there is nothing to prevent a compiler from generating equivalent bytecode.

Note that any generic dynamic method call operation similar to Method.invoke, capable of calling methods with arbitrary signatures, will require marshalling of arguments and casting of results, as well as wrapping and unwrapping of arguments and results of primitive types. No improvement to Method.invoke itself, say, to cache the results of the runtime check on the class of the receiver object or on the method access permissions, can avoid this overhead, which is inherent in its signature.

For the sake of argument, let us temporarily suppose that for some reason we find the Visual J++ delegation notation desirable. A simpler, more straightforward, and more efficient translation of the delegate construct could use inner classes as follows:

abstract class IntOp {
  abstract long invoke(int a, int b);
}

public class Example {
  long add(int a, int b) {
    return a + b;
  }
  public void doIt() {
    IntOp op = new IntOp() {
      // Bridge method.
      long invoke(int a, int b) { return add(a, b); }
    };
    long result = op.invoke(2, 3);
  }
}

This translation avoids marshalling, wrapping, and casting overhead by exploiting the Java VM's built-in method dispatching mechanism, in which almost all of the per-invocation runtime overhead has been eliminated in favor of one-time checks when the class is loaded or the method first referenced. But why stop here? Let's get rid of that annoying bridge method, expanding the call to add in-line:

abstract class IntOp {
  abstract long invoke(int a, int b);
}

public class Example {
  public void doIt() {
    IntOp op = new IntOp() {
      long invoke(int a, int b) { return a + b; }
    };
    long result = op.invoke(2, 3);
  }
}

We have now gone full circle, and arrived where we should have started. This is the code we could have just as easily written in the first place, without using any non-standard language extensions. The delegate extension in Visual J++ may be "syntactic sugar," but it isn't very sweet.