What is Method Handles in Java?

Original author: baeldung
  • Transfer
  • Tutorial

1. Introduction


In this tutorial, we will look at an important API introduced in Java 7 and extended in new versions, java.lang.invoke.MethodHandles .



We learn what method handles are, how to create and use them.


2. What is Method Handles?


In the API documentation, the method handle has the following definition:


A method handle is a typed, executable reference to a base method, constructor, field, or other low-level operation with additional transformations of the arguments or return values.

In other words, method handles are a low-level mechanism for finding, adapting, and invoking methods. The method objects handles are immutable and do not have a display state.


To create and use, MethodHandleyou need to perform 4 steps:


  1. Create search descriptor - lookup
  2. Declare method type
  3. Search for method handle
  4. Call method handle

2.1. Method Handles vs Reflection


Method handles were presented to function along with the java.lang.reflect API , since They are created for different purposes and differ in their characteristics.


From a performance point of view, the MethodHandles API can be much faster than the Reflection API, since access checks are performed at the time of creation rather than execution . In the presence of a security manager, this difference increases, because searching for classes and getting their items are subject to additional checks.


However, performance is not the only indicator of the optimality of the task, it is necessary to take into account that the MethodHandles API is more difficult to use due to the lack of such mechanisms as obtaining class methods, checking access markers, etc.


Despite this, the MethodHandles API allows you to curry methods, change the type and order of parameters.


Now, knowing the definition and purpose of the MethodHandles API, we can work with them. Let's start with the search methods.


3. Making a Lookup


The first thing to do when we want to create a method handle is to get a lookup, a factory object responsible for creating method handles for methods, constructors, and fields visible to the lookup class.


Using the MethodHandles API, you can create a lookup object with different access modes.


Create a lookup that provides access to public methods:


MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();

However, if we need access to the private and protected methods, we can instead use the lookup () method:


MethodHandles.Lookup lookup = MethodHandles.lookup();

4. Create MethodType


To create a MethodHandle lookup object, you must specify a type, and this can be done using the MethodType class.


In particular, MethodType represents the arguments and the type of the return value, received and returned by the method handle, or transmitted and expected by the calling code.


The MethodType structure is simple; it is formed by the return type along with the corresponding number of parameter types, which must fully relate between the method handle and the calling code.


Like MethodHandle, all instances of MethodType are immutable.


Let's see how to define MethodType, which sets the class java.util.Listas the return type and the Object array as the data entry type:


MethodType mt = MethodType.methodType(List.class, Object[].class);

In case the method returns a simple or void value type, we use a class representing these types (void.class, int.class …).


Define a MethodType that returns an int value and accepts an Object:


MethodType mt = MethodType.methodType(int.class, Object.class);

You can begin to create MethodHandle.


5. Search MethodHandle


After we set the method type, to create the MethodHandle, we need to find it using the lookup object or publicLookup, which also gives the original class and method name.


Lookup provides a set of methods that allow you to find a method handle in the optimal way, taking into account the method scope. Consider the basic approaches, starting with the simplest.


5.1. Method Handle for methods


Using the method, findVirtual()you can create a MethodHandle for an instance method. Create it based on the concat()class method String:


MethodType mt = MethodType.methodType(String.class, String.class);
MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt);

5.2. Method Handle for static methods


To gain access to a static method, you can use the method findStatic():


MethodType mt = MethodType.methodType(List.class, Object[].class);
MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt);

In this case, we created a method handle of a method that converts an array of a type Objectinto a list List.


5.3. Method Handle for constructors


You can access the constructor using the method findConstructor().


Create a method handle with the behavior of the Integer class constructor with the String parameter:


MethodType mt = MethodType.methodType(void.class, String.class);
MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt);

5.4. Method Handle for fields


With the help of the method handle, you can also access the fields.


Let's start with the definition of the Book class:


publicclassBook{
    String id;
    String title;
    // constructor
}

As an initial condition, we have direct visibility between the method handle and the declared property, so you can create a method handle with the behavior of a get method:


MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class);

For more information on variable / field management, see the Java 9 Variable Handles Demystified article , where we talk about the java.lang.invoke.VarHandle API , introduced in Java 9.


5.5. Method Handle for Private Methods


You can create a method handle for a private method using the java.lang.reflect API .
Let's start by creating a private method for the Book class:


private String formatBook(){
    return id + " > " + title;
}

Now we can create a method handle with the behavior of the method formatBook():


Method formatBookMethod = Book.class.getDeclaredMethod("formatBook");
formatBookMethod.setAccessible(true);
MethodHandle formatBookMH = lookup.unreflect(formatBookMethod);

6. Calling Method Handle


Once we have created our method handle, proceed to the next step. MethodHandle class gives us 3 different ways to handle the call method: invoke(), invokeWithArugments()and invokeExact().


Let's start with the way invoke.


6.1. Challenge Method Handle


When using the method, the invoke()number of arguments (arity) is fixed, but it is possible to perform type casting and packing / unpacking arguments and return value types.


Now let's see how we can use it invoke()with a packed 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, replaceMHarguments are required char, but the method invoke()unpacks the Character argument before it is executed.


6.2. Call with arguments


Calling the method handle using the method invokeWithArgumentshas the least restrictions.


In fact, in addition to checking types and packing / unpacking arguments and return values, it allows you to make calls with a variable number of parameters.


In practice, we can create an Integer list, having an array of int values ​​of unknown length:


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. Exact call


If it is necessary for us that the method handle be executed more restrictively (by the set of arguments and their type), we use the method invokeExact().


In fact, it does not provide the ability to cast class types and requires a fixed set of arguments.


Let's see how you can add two int values ​​using 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);

In this case, if you pass a invokeExactnumber to the method that is not an int, we will get it when we call it WrongMethodTypeException.


7. Working with arrays


MethodHandles can work not only with fields and objects, but also with arrays. Using the asSpreader()API, you can create a method handle that supports arrays as positional arguments.


In this case, the method handle takes an array, distributing its elements as positional arguments, and optionally the length of the array.


Let's see how to get the method handle to check if the array arguments are identical strings:


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. Refine Method Handle


Once the method handle is set, you can refine it by binding to the argument, without calling the method.


For example, in Java 9, this trick is used to optimize string concatenation.


Let's see how you can perform concatenation by tying the suffix to 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 updates


In Java 9, several changes were made to the MethodHandles API to simplify their use.


Updates cover 3 main aspects:


  • Lookup functions - allow searching from different contexts and support non abstract methods in interfaces.
  • Argument operations - improving the functionality of folding, collecting, and distributing arguments.
  • Further combinations - the addition cycle of operations ( loop, whileLoop, doWhileLoop, ...) and improved handling exceptions via tryFinally.

These changes entailed other useful innovations:


  • Improved JVM Compiler Optimization
  • Instance reduction
  • Specifying MethodHandles API Usage

A more detailed list of changes is available in the Javadoc API MethodHandles .


10. Conclusion


In this article, we introduced the MethodHandles API, and also learned what Method Handles are and how to use them.


We also described how it is associated with the Reflection API. Since the call to method handles is a fairly low-level operation, their use is justified only if they are exactly suitable for your tasks.


As usual, all the source code for the article is available on Github .


Also popular now: