Sat, 20 May 2006

Choosing Member Functions at Runtime in Java

Have you ever wanted to call a member function in your class, but not known what it will be at compile time? I'm writing a SAX parser and would like a function for every element name. I could write a massive switch statement in the startElement function, but this will quite quickly become unmanagable for a large schema. The alternative is to look to see if a particular member function exists and call it.

To do this little bit of magic we need to use Java's introspection API. The first thing to do is to get a Class object for our class. We can do that by calling:

Class klass = this.getClass();

We can then look up the method we are looking for using Class.getMethod, but this function requires an array of types that the method we are looking for takes as parameters, so we get the right version of an overloaded method. We can do this with:

Class[] arguments = { Int.class, String.class, URL.class};
Method method = klass.getMethod("foo", arguments);

Now we have our method, we can call it using the Method.invoke() call. This takes an object as the first parameter, which we can use this, and an array of Objects for the parameters.

Object[] values = {bar, baz, quux};
method.invoke(this, values);

But what happens if our class has no member method called foo()? Well, Class.getMethod() will throw a NoSuchMethodException, so we can just throw a try/catch block around the code to deal with unhandled functions. It's worth pointing out that Class.getMethod() also throws SecurityException and Method.invoke() throws IllegalAccessException, IllegalArgumentException and InvocationTargetException, so you'll want to catch Exception too.

We can chain some of these calls together and the result for my SAX parser is:

public void startElement(String uri, String localName, String qName, Attributes atts) 
            throws SAXException {
   try {
       Class[] argTypes = { String.class, String.class, String.class,
               Attributes.class };
       Object[] values = { uri, localName, qName, atts };
       this.getClass().getMethod("startElement_" + localName, argTypes)
               .invoke(this, values);
   } catch (NoSuchMethodException e) {
       log.debug("unhandled element " + localName);
   } catch (Exception e) {
       e.printStackTrace();
   }
}

With this arrangement, when I want to handle a new element in my code I can just make a function like:

public void startElement_foo(String uri, String localName, String qName, Attributes atts)
            throws SAXException {
   ... 
}
[] | # Read Comments (4) |

Comments

Thanks a lot!
Posted by Jochen at Sat May 20 17:58:27 2006
You sure you don't want to move over to the development team?
Posted by Miles Barr at Sat May 20 19:16:35 2006
Just used this trick --- thanks.
Posted by resiak at Mon Aug 14 22:30:39 2006
I like this design pattern, but the use of reflection here seems like a big performance problem.


At the least, you could at runtime (ie: in a static{} block or constructor) determine the set of methods and store them in a Map<String, Method>. Then, for each node, you can skip the method lookup and only call Map.get(key).invoke().

A much better way to do this, in my opinion, is to just write a macro/script to do this up front and hardcode a big if/then/else block.

$.02
Posted by Joseph at Wed Sep 20 21:31:19 2006

Name:


E-mail:


URL:


Comment:


Please enter "fudge" to prove you are a human