Sunday, January 16, 2005

Wrong method with reflection

Try this:

public Method getUnaryMethod(final Class clazz,
        final String name, final Class parameterType)
        throws NoSuchMethodException {
    return clazz.getMethod(name, parameterType);
}

Looks easy, doesn't it? But:

private static class Overloaded {
    public Class foo(final Number number) { return number.getClass(); }
    // public Class foo(final Integer integer) { return integer.getClass(); }
}

void void testGetUnaryMethod() throws Exception {
    final Class expected = Integer.class;
    final Class actual = getUnaryMethod(Overloaded.class,
            "foo", expected).getParameterTypes()[0];

    assertEquals(expected, actual);
}

The test throws! To pass the test, uncomment the second definition of foo. Unfortunately, I'm working on a dispatch system and this sort of thing is death. Why? Because I cannot repeat with reflection this simple call:

new Overloaded().foo(new Integer(3));

There is a solution, though, replace getMethod with:

public static Method getMethod(final Class clazz,
        final String name, final Class parameterType)
        throws NoSuchMethodException {
    for (Class c = parameterType; null != c; c = c.getSuperclass())
        try {
            return clazz.getMethod(name, c);

        } catch (final NoSuchMethodException e) { }

    return clazz.getMethod(name, parameterType);
}

I look up the inheritance tree for parameterType until I find the first exact match via reflection and return that method. The catch block just lets me retry the lookup one branch higher in the tree. If I exhaust the tree, there is no match even with inheritance, in which case I retry the original lookup so as to throw the same exception as Class.getMethod(String, Class[]) would.

Now I can dispatch dynamically with reflection the same as would the Java runtime. The only drawback is that this simple algorithm only works to vary a single parameter type. For true multiple dispatch, I need a better algorithm to vary more than one parameter type and detect ambiguities: this would let me work methods with more than one argument.

5 comments:

Anonymous said...

Inquiring minds want to know - what kind of project requires you to (in effect) reimplement the perfectly serviceable method dispatch provided in the JVM ? ;)

Brian Oxley said...

Good question, eh? Java method dispatch works with static method calls (static in the sense of compiled, not in the sense of the static keyword). If you want dynamic calls, say you are reading from a script, you have to do something else. In my case, the problem relates to the Mercury project (http://binkley.fastmail.fm/) for a talk I'm giving with a former coworker.

We have a light-weight messaging system, and route messages not by some kind of "topic" but by the types of the messages. You can have a receive(Foo) and a receive(SubFoo) so that the first handles all regular Foos, and the second just SubFoos. The system is held together with reflection.

There is also an annotation-based version, but that turns out to be reflection under the hood.

Anonymous said...

http://www.adtmag.com/java/article.asp?id=4276&mon=8&yr=2001

Anonymous said...

Your solution works fine for inherence, but if we use interfaces it does no longer works :(.

E.g.: If the method foo had this signiture

public Class foo(final InterfaceNumber number) { return number.getClass(); }

And by hypoteses Number implements InterfaceNumber your testGetUnaryMethod no longer passes.

Greetings.

Brian Oxley said...

Yep, sure falls down in that case, doesn't it? I hit this before when working on type-based message dispatch (see various other posts) and have given up on using an inheritance-based approach. When I tried annotations, however, the scales fell my eyes! I don't have to nastily recreate the JVM's method dispatch, but still get to use bindings and activations so I can delay and reorder method calls for messaging. Very elegant.