Google
 

Tuesday, June 29, 2010

Java Callbacks using java.lang.reflect

Java does not have callbacks. Most commonly, I see people working around this by implementing the Observer Pattern, or simply having an interface for each method they need to use as a callback.

On a recent project, I felt the need for more direct callbacks in my Java code. Java's reflection package seemed to hold my answer. Given a Class, a method name, and (in the case of overloaded methods) a list of parameter types, you can retrieve an instance of the Method class. Then, given a list of arguments to pass to the method, it can be invoked via reflection.

Java reflection isn't particularly fast, so I recommend against this approach in applications where performance is important. But reflection works, so I present my Callback class here: package com.bs; import java.lang.reflect.*; /** * Implements callback functionality for Java. * Callbacks are implemented using reflection, so should * be avoided if possible. * * @author Brian Shields <http://clockworkgear.blogspot.com/> */ public class Callback { private Object parentObj; private Method method; private Class<?>[] parameters; public Callback(Class<?> clazz, String methodName, Object parentObj) { // Find a method with the matching name Method[] allMethods; try { allMethods = clazz.getMethods(); } catch(SecurityException se) { allMethods = new Method[0]; } int count = 0; Method single = null; for(Method m : allMethods) { if(m.getName().equals(methodName)) { single = m; count++; } // Can't have more than one instance if(count > 1) throw new IllegalArgumentException(clazz.getName() + " has more than one method named " + methodName); } if(count == 0) // No instances found throw new IllegalArgumentException(clazz.getName() + " has no method named " + methodName); this.parentObj = parentObj; this.method = single; this.parameters = single.getParameterTypes(); } public Callback( Class<?> clazz, String methodName, Object parentObj, Class<?>...parameters) { try { this.method = clazz.getMethod(methodName, parameters); } catch(NoSuchMethodException nsme) { nsme.printStackTrace(); } catch(SecurityException se) { se.printStackTrace(); } this.parentObj = parentObj; this.parameters = parameters; } public Object call(Object...vals) { if(parameters.length != vals.length) throw new IllegalArgumentException( "Wrong number of method parameters given. Found " + vals.length + ", expected " + parameters.length); Object ret = null; try { ret = method.invoke(parentObj, vals); } catch(IllegalAccessException iae) { iae.printStackTrace(); } catch(InvocationTargetException ite) { ite.printStackTrace(); } return ret; } }

2 comments:

Unknown said...

This information could be made more useful if you also provided a simple example on how to use it.

Unknown said...

Sorry for the poor formatting on this site, but here is a followup providing sample use of this example for anyone who follows behind me.


public class Test {

public void Test() {}

public static void main(String[] args) throws Exception {

Test obj = new Test(); // a dummy object to pass
Callback method;

method = new Callback(Test.class, "ClassCallBackWithoutParam", null);
method.call();

method = new Callback(Test.class, "ClassCallBackWithParam", null, String.class);
method.call("hello");

method = new Callback(Test.class, "MemberCallBackWithoutParam", obj);
method.call();

method = new Callback(Test.class, "MemberCallBackWithParam", obj, String.class);
method.call("hello");

method = new Callback(Test.class, "MemberCallBackWithParameters", obj, String.class, int.class, Long.class);
method.call("hello", 1, (Long)1234567890L);
}

public static void ClassCallBackWithoutParam() {
System.out.println("Method.ClassCallBackWithoutParam");
}

public static void ClassCallBackWithParam(String msg) {
System.out.println("Method.ClassCallBackWithParam got '" + msg + "\"'");
}

public void MemberCallBackWithoutParam() {
System.out.println("Method.MemberCallBackWithoutParam");
}

public void MemberCallBackWithParam(String msg) {
System.out.println("Method.MemberCallBackWithParam got '" + msg + "\"'");
}

public void MemberCallBackWithParameters(String msg, int value, Long longvalue) {
System.out.println("Method.MemberCallBackWithParam got '" + msg + "' int: " + value + ", Long: " + longvalue);
}

}