1. Introduction
In this article, we’re going to explore an important API that was introduced in Java 7 and enhanced in the following versions, the java.lang.invoke.MethodHandles.
In particular, we’ll learn what method handles are, how to create them and how to use them.
2. What Are Method Handles?
Coming to its definition, as stated in the API documentation:
A method handle is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values.
In a simpler way, method handles are a low-level mechanism for finding, adapting and invoking methods.
Method handles are immutable and have no visible state.
For creating and using a MethodHandle, 4 steps are required:
- Creating the lookup
- Creating the method type
- Finding the method handle
- Invoking the method handle
2.1. Method Handles vs Reflection
Method handles were introduced in order to work alongside the existing java.lang.reflect API, as they serve different purposes and have different characteristics.
From a performance standpoint, the MethodHandles API can be much faster than the Reflection API since the access checks are made at creation time rather than at execution time. This difference gets amplified if a security manager is present, since member and class lookups are subject to additional checks.
However, considering that performance isn’t the only suitability measure for a task, we have also to consider that the MethodHandles API is harder to use due to the lack of mechanisms such as member class enumeration, accessibility flags inspection and more.
Even so, the MethodHandles API offers the possibility to curry methods, change the types of parameters and change their order.
Having a clear definition and goals of the MethodHandles API, we can now begin to work with them, starting from the lookup.
3. Creating the Lookup
The first thing to do when we want to create a method handle is to retrieve the lookup, the factory object that is responsible for creating method handles for methods, constructors, and fields, that are visible to the lookup class.
Through the MethodHandles API, it’s possible to create the lookup object, with different access modes.
Let’s create the lookup that provides access to public methods:
MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
However, in case we want to have access also to private and protected methods, we can use, instead, the lookup() method:
MethodHandles.Lookup lookup = MethodHandles.lookup();
4. Creating a MethodType
In order to be able to create the MethodHandle, the lookup object requires a definition of its type and this is achieved through the MethodType class.
In particular, a MethodType represents the arguments and return type accepted and returned by a method handle or passed and expected by a method handle caller.
The structure of a MethodType is simple and it’s formed by a return type together with an appropriate number of parameter types that must be properly matched between a method handle and all its callers.
In the same way as MethodHandle, even the instances of a MethodType are immutable.
Let’s see how it’s possible to define a MethodType that specifies a java.util.List class as return type and an Object array as input type:
MethodType mt = MethodType.methodType(List.class, Object[].class);
In case the method returns a primitive type or void as its return type, we will use the class representing those types (void.class, int.class …).
Let’s define a MethodType that returns an int value and accepts an Object:
MethodType mt = MethodType.methodType(int.class, Object.class);
We can now proceed to create MethodHandle.
5. Finding a MethodHandle
Once we’ve defined our method type, in order to create a MethodHandle, we have to find it through the lookup or publicLookup object, providing also the origin class and the method name.
In particular, the lookup factory provides a set of methods that allow us to find the method handle in an appropriate way considering the scope of our method. Starting with the simplest scenario, let’s explore the principal ones.
5.1. Method Handle for Methods
Using the findVirtual() method allow us to create a MethodHandle for an object method. Let’s create one, based on the concat() method of the String class:
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
5.2. Method Handle for Static Methods
When we want to gain access to a static method, we can instead use the findStatic() method:
MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);
In this case, we created a method handle that converts an array of Objects to a List of them.
5.3. Method Handle for Constructors
Gaining access to a constructor can be done using the findConstructor() method.
Let’s create a method handles that behaves as the constructor of the Integer class, accepting a String attribute:
MethodType mt = MethodType.methodType(void.class, String.class);
MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);
5.4. Method Handle for Fields
Using a method handle it’s possible to gain access also to fields.
Let’s start defining the Book class:
public class Book {
String id;
String title;
// constructor
}
Having as precondition a direct access visibility between the method handle and the declared property, we can create a method handle that behaves as a getter:
MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);
For further information on handling variables/fields, give a look at the Java 9 Variable Handles Demystified, where we discuss the java.lang.invoke.VarHandle API, added in Java 9.
5.5. Method Handle for Private Methods
Creating a method handle for a private method can be done, with the help of the java.lang.reflect API.
Let’s start adding a private method to the Book class:
private String formatBook() {
return id + " > " + title;
}
Now we can create a method handle that behaves exactly as the formatBook() method:
Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);
MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);
6. Invoking a Method Handle
Once we’ve created our method handles, use them is the next step. In particular, the MethodHandle class provides 3 different way to execute a method handle: invoke(), invokeWithArugments() and invokeExact().
Let’s start with the invoke option.
6.1. Invoking a Method Handle
When using the invoke() method, we enforce the number of the arguments (arity) to be fixed but we allow the performing of casting and boxing/unboxing of the arguments and return types.
Let’s see how it’s possible to use the invoke() with a boxed argument:
MethodType mt = MethodType.methodType(String.class, char.class, char.class);
MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt);
String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a');
assertEquals("java", output);
In this case, the replaceMH requires char arguments, but the invoke() performs an unboxing on the Character argument before its execution.
6.2. Invoking With Arguments
Invoking a method handle using the invokeWithArguments method, is the least restrictive of the three options.
In fact, it allows a variable arity invocation, in addition to the casting and boxing/unboxing of the arguments and of the return types.
Coming to practice, this allows us to create a List of Integer starting from an array of int values:
MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt);
List<Integer> list = (List<Integer>) asList.invokeWithArguments(1,2);
assertThat(Arrays.asList(1,2), is(list));
6.3. Invoking Exact
In case we want to be more restrictive in the way we execute a method handle (number of arguments and their type), we have to use the invokeExact() method.
In fact, it doesn’t provide any casting to the class provided and requires a fixed number of arguments.
Let’s see how we can sum two int values using a method handle:
MethodType mt = MethodType.methodType(int.class, int.class, int.class);
MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt);
int sum = (int) sumMH.invokeExact(1, 11);
assertEquals(12, sum);
If in this case, we decide to pass to the invokeExact method a number that isn’t an int, the invocation will lead to WrongMethodTypeException.
7. Working With Array
MethodHandles aren’t intended to work only with fields or objects, but also with arrays. As a matter of fact, with the asSpreader() API, it’s possible to make an array-spreading method handle.
In this case, the method handle accepts an array argument, spreading its elements as positional arguments, and optionally the length of the array.
Let’s see how we can spread a method handle to check if the elements within an array are equals:
MethodType mt = MethodType.methodType(boolean.class, Object.class);
MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt);
MethodHandle methodHandle = equals.asSpreader(Object[].class, 2);
assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" }));
8. Enhancing a Method Handle
Once we’ve defined a method handle, it’s possible to enhance it by binding the method handle to an argument without actually invoking it.
For example, in Java 9, this kind of behaviour is used to optimize String concatenation.
Let’s see how we can perform a concatenation, binding a suffix to our concatMH:
MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);
MethodHandle bindedConcatMH = concatMH.bindTo("Hello ");
assertEquals("Hello World!", bindedConcatMH.invoke("World!"));
9. Java 9 Enhancements
With Java 9, few enhancements were made to the MethodHandles API with the aim to make it much easier to use.
The enhancements affected 3 main topics:
- Lookup functions – allowing class lookups from different contexts and support non-abstract methods in interfaces
- Argument handling – improving the argument folding, argument collecting and argument spreading functionalities
- Additional combinations – adding loops (loop, whileLoop, doWhileLoop…) and a better exception handling support with the tryFinally
These changes resulted in few additional benefits:
- Increased JVM compiler optimizations
- Instantiation reduction
- Enabled precision in the usage of the MethodHandles API
Details of the enhancements made are available at the MethodHandles API Javadoc.
10. Conclusion
In this article, we covered the MethodHandles API, what they’re and how we can use them.
We also discussed how it relates to the Reflection API and since the method handles allow low-level operations, it should be better to avoid using them, unless they fit perfectly the scope of the job.
As always, the complete source code for this article is available over on Github.