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