|
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.
|
|