📜 ⬆️ ⬇️

What is Method Handles in Java?

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 MethodHandle you 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 return type, taken 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 java.util.List class as the return type and the Object array as the input 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 findVirtual() method, you can create a MethodHandle for an instance method. Create it based on the class String concat() method:


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

5.2. Method Handle for static methods


To access the static method, you can 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 of a method that converts an array of type Object into a List .


5.3. Method Handle for constructors


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


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:


 public class Book { 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 formatBook() method:


 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. The MethodHandle class gives us 3 different ways to invoke a method handle: invoke() , invokeWithArugments() and invokeExact() .


Let's start with the invoke method.


6.1. Challenge Method Handle


When using the invoke() method, the 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 invoke() can be used 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, replaceMH requires char arguments, but the invoke() method unpacks the Character argument before it is executed.


6.2. Call with arguments


Calling the method handle using the invokeWithArguments method has 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 we need the method handle to be more limited (by the set of arguments and their type), we use the invokeExact() method.


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 number to the invokeExact method that is not an int, we will get a 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 specified, 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:



These changes entailed other useful innovations:



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 exactly fit your needs.


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


')

Source: https://habr.com/ru/post/431922/


All Articles