With advent of modern Java versions we are introduced to MethodHandle and VarHandle in the toolkit to get access over other Java methods and fields in different classes.
These handles are essentially typed references to either a method or a variable. MethodHandle can be used find and invoke a method just like how we do it with reflection. However, MethodHandle provides a more cleaner and safer way of doing the same thing.
Steps Involved
The Handles comes with various lookup methods, which actually helps to find the metadata of classes that we want to access.
Next since now we have the handle for our desired class’ metadata, we go on using MethodHandle or VarHandle to actually invoke any methods or access fields respectively.
Is This Better Than Reflection ?
Let’s first understand what is reflection and why we need reflection in Java. Java Reflection is a process of checking or modifying the run time behaviour of a class during the run time. We use reflection to call specific methods or access variables of class which we need to invoke during runtime (with prior checks). These are usually methods which we cannot define directly in our code with the new keyword.
A good example of this is JUnit which will call all the methods tagged with the @Test annotation to run the unit test case using reflection to look through all the classes for methods tagged. Similar one can build the working of lombok when we use @Getter or @Setter in Spring Boot projects in our entity. However, it is to be kept in mind that lombok doesn’t use reflection at runtime. Instead, Lombok works at compile-time through annotation processing and bytecode manipulation.
Using Reflection
Now, let us look at code examples of how we can use these three. Consider we have a class like below:
public class TestClass {
private String name;
public TestClass(String name) {
this.name = name;
}
public String greetUser() {
return "Good Morning " + name;
}
}
Now let’s see how we can use reflection to access the method greetUser()
Class clas = Class.forName("TestClass");
Method method = clas.getMethod("greetUser", String.class);
Object obj = clas.newInstance()
String result = (String) method.invoke(obj);\
Using MethodHandle
Using method handle, the above code will become like the below:
Class<?> clas = objectInstance.getClass();
MethodHandle handle = MethodHandles.lookup().findVirtual(clas, "greetUser", methodType(String.class));
String value = (String) handle.invoke(obj);
Using VarHandle
Now, let us try to access the field name using VarHandle.
private static final VarHandle handle;
static {
try {
Class<?> clas = objectInstance.getClass();
handle = MethodHandles.privateLookupIn(clas, MethodHandles.lookup()).findVarHandle(clas, "name", String.class);
String value = (String) handle.get(objectInstance);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
As a convention it is recommended to declare the VarHandle as a static final field and then initialize them in static blocks for better performance. This is so that the JVM can inline the information during compile time. But this is obviously not possible in cases where where we are unaware of the class name at compile time.