Typically, introspective invocation on a method takes two stages: First you need to locate the target method to invoke and then you invoke the method on an target instance, providing it with parameters.
In this process, the most expensive operation is to locate the target method in a class (e.g. Method method = TargetClass.getMethod(Class[] signature ...)
Once you get hold to the Method object, invoking the method on an object, like: method.invoke(targetObj, param,...)
is a lightweight operation just marginally more expensive than direct method invocation.
To demonstrate this, I just did a quick & dirty comparison of the three methods with the following results (you need to compare them relative to each other):
- Reflect method every time + invocation: 167ms
- First cache methods from class + invocation: 36 ms
- Direct invocation: 17ms
Note that introspection has a fix performance cost, so the more computation that the method is doing, the closer these numbers will be.
I have used this method-caching approach in previous projects where performance was highly important. In practice you observe that the actual method execution time makes the cost of introspection neglegible. (cfr . Amdahal's law )
Test code (bear the quick & dirtiness)
import java.lang.reflect.Method;
import java.util.Random;
/**
* Created by maasg on 5/10/14.
*/
public class Instrospection {
public static void main(String [] params) throws Exception {
Random ran = new Random();
String[] methods = new String[] {"method1", "method2", "method3"};
Target target = new Target();
// Warmup
for (int i=0; i<1000; i++) {
String methodName = methods[ran.nextInt(3)];
String param = new Integer(ran.nextInt()).toString();
Method method = Target.class.getMethod(methodName, String.class);
}
StringBuilder builder = new StringBuilder();
long t0 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
String methodName = methods[ran.nextInt(3)];
String param = new Integer(ran.nextInt()).toString();
Method method = Target.class.getMethod(methodName, String.class);
Object result = method.invoke(target, "param");
builder.append(result.toString());
}
System.out.println("Elapsed 1: "+(System.currentTimeMillis()-t0));
Method[] invokeMethods = new Method[] {
Target.class.getMethod(methods[0], String.class),
Target.class.getMethod(methods[1], String.class),
Target.class.getMethod(methods[2], String.class),
};
builder = new StringBuilder();
long t1 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
String param = new Integer(ran.nextInt()).toString();
Method method = invokeMethods[ran.nextInt(3)];
Object result = method.invoke(target, "param");
builder.append(result.toString());
}
System.out.println("Elapsed 2: "+(System.currentTimeMillis()-t1));
builder = new StringBuilder();
long t2 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
Object result = null;
String param = new Integer(ran.nextInt()).toString();
switch (ran.nextInt(3)) {
case 0: result = target.method1(param);
case 1: result = target.method2(param);
case 2: result = target.method3(param);
}
builder.append(result.toString());
}
System.out.println("Elapsed 3: "+(System.currentTimeMillis()-t2));
}
}