One of the things that distinguishes a veteran Java developer is familiarity with reflection and its modern alternatives. Reflection gives you superpowers, but it is cumbersome, error-prone, and a performance bottleneck. Modern Java is working to replace reflection with standardized options, including MethodHandle and VarHandle. Like reflection, these classes give you the ability to access methods and fields on objects but in a cleaner API.
The power of handles
As the names imply, both MethodHandle
and VarHandle
give you “handles,” which are variables to reference the meta-properties of an object. These handles give you the power to deal with methods and fields directly. They are special variables that refer to parts of the runtime environment otherwise hidden from your code.
The starting point for these capabilities is the various lookup methods on MethodHandle
, which offer a modern way to programmatically find the metadata for classes. This is similar to the old reflection API’s methods like getDeclaredMethod
, but with more structure and safety.
Once you have the handle for the class metadata, you can use MethodHandle and VarHandle to programmatically make calls against the method and fields that exist on a class instance. Under the hood, the JVM manages these approaches, which generally yields better performance than you would get with reflection.
Method and variable handles vs. Java reflection
To truly understand MethodHandles
and VarHandles
—what they do and why they are useful—it’s helpful to know a few things about reflection in Java. This will help you understand why reflection has evolved into these newer APIs. If you already know about reflection, the gist will be clear. If not, some examples will help illustrate.
The fundamental question is: What need do these technologies—reflection, method handles, variable handles—fulfill? Why, when we can simply instantiate an object, call its public methods, and access its public members, would we do these things programmatically instead?
In many cases, you actually can’t access what you need via public methods, so you have to go around the normal routes. This mostly happens when you are writing something like framework code that operates against a range of classes and does something non-standard with them.
As an example, think of a persistence framework. You need to map classes to and from tables, and so you need to introspect the classes to understand what fields and methods they have. This scenario also comes up in application code, especially if you need to access an otherwise inaccessible part of a legacy library.
Deciding which technology to use requires understanding what is needed. If you can resolve the issue with normal Java calls, that’s the way to go. If you need something more sophisticated, first look to the standard APIs, like MethodHandles
and VarHandles
. Only if these all fail to deliver should you fall back on reflection.
Examples should help clarify why the JDK prefers handles to traditional Java reflection.
Using reflection to access a method
We’ll start with an example of reflection because it is familiar and will give us a known reference. Just remember that it is the solution of last resort.
Say you have this class:
public class MyClass {
private String name;
public MyClass(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
This is a very simple affair: just a class to hold string name and a getter and setter. To create this class, we can use normal instantiation:
MyClass objectInstance = new MyClass("John Doe");
Here’s how to access the method using reflection:
Class> clazz = objectInstance.getClass();
Method method = clazz3.getDeclaredMethod("getName");
String value = (String) method.invoke(objectInstance);
System.out.println(value); // prints "John Doe"
Using MethodHandles to access a method
Method handles give us the same kind of power as reflection, but with safer syntax:
Class> clazz = objectInstance.getClass();
MethodHandle handle = MethodHandles.lookup().findVirtual(clazz, "getName", methodType(String.class));
String value = (String) handle.invoke(objectInstance);
System.out.println(value); // Prints “John Doe”
We start off the same way, by obtaining the class from our instance. Then, we use the lookup().findVirtual() method on MethodHandles
. This is one of the chief things that MethodHandles
was designed to do: provide a cleaner, JDK-approved way to look up a method. This approach is also enhanced for JVM optimization.
Next, we would call the method with the handle using handle.invoke
and passing in the object instance.
Directly accessing fields
Now let’s say our earlier class, MyClass
, has that name
field on it but there is no accessor. We need something stronger to get at it now because we’re going to directly access the private member. Here’s how we’d do it using standard reflection:
Class> clazz = objectInstance.getClass();
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
String value = (String) field.get(objectInstance);
System.out.println(value); // prints “John Doe”
Notice we are again directly working with the metadata of the object, like its class and the field on it. We can manipulate the accessibility of the field with setAccessible
(this is considered risky because it might alter the restrictions that were put on the target code as written). This is the essential part of making that private field visible to us.
Now let’s do the same thing using variable handles:
Class>l clazz = objectInstance.getClass();
VarHandle handle = MethodHandles.privateLookupIn(clazz,
MethodHandles.lookup()).findVarHandle(clazz, "name", String.class);
String value = (String) handle.get(objectInstance);
System.out.println(value4); // prints "John Doe"
Here, we use privateLookupIn
because the field is marked private. There is also a generic lookup()
, which will respect the access modifiers, so it’s safer but won’t find the private field.
Although the above code works, it is recommended to instantiate the handle itself statically for performance reasons, like so:
private static VarHandle HANDLE;
static {
try {
HANDLE = MethodHandles.privateLookupIn(MyClass.class, MethodHandles.lookup()).findVarHandle(MyClass.class, "name", String.class);
} catch (Throwable t){
throw new RuntimeException(t);
}
}
// …
System.out.println("static: " + HANDLE.get(objectInstance));
Here, we have statically instantiated the HANDLE
variable and then used it later in the normal code flow. This also highlights the fact that the handle itself is defined for the type (MyClass
) and then reused for the instances (objectInstance
).
Note that directly instantiating the handle requires that you know the name of the class. If you don’t know the name of the class—such as if you are taking a String
and reflectively instantiating that class and then accessing its fields—you can’t use this approach.
Limitations of method and var handles
Although they bring serious power into the standardized JDK—this article is a mere sampling of what they can do—method and variable handles are not intended to cover all of the capabilities found in the Java Reflection API. They cover a focused range: finding class metadata and using it to access methods and fields outside of the normal Java restrictions. The rest of the power of reflection, found in sun.misc.Unsafe
, is gradually being replaced by other packages.
As previously mentioned, MethodHandles
and VarHandle
do not support instantiating classes, which presents limitations in some scenarios.
Why it’s time for an alternative to reflection
It’s worth taking a moment to convince yourself that migrating away from reflection is necessary. The performance of method and var handles, if you research the benchmarks, is not universally considered superior to reflection. On the other hand, they are safer, more idiomatic, and the JVM codebase is adopting these approaches. It’s only a matter of time before you will need to begin using them.
In benchmarks, statically declaring the handle, as we did above, dramatically improves performance. This is because the JVM can inline that information at compile-time. But, as noted, doing that is not always possible—for example, if you don’t know the name of the class at compile time.
Beyond performance, though, and even beyond correctness, reflection is being deprecated. Eventually, the work of migrating will be required no matter what. Now is the time to start moving those parts of your codebase that have modern replacements like MethodHandles
and VarHandle
.