[java] In-depth analysis of the principle of try with resource how java makes method calls through method handles

If this code is copied and used, it is very simple, but the derivation process is really arduous. The blogger quarreled with a certain T for a dozen or twenty lines of code, and at the end the writing method given by a certain T was still wrong. It is not easy to write this demo. It is recommended that students who read this blog go to further study the relevant content by themselves. After all, the knowledge points in the field where T makes mistakes are not so easy to master. We have to know how to troubleshoot mistakes.

Preface: Why do bloggers know the concept of method handle?

We know that in jdk7, the use of try with resource was introduced to replace try catch finally to a certain extent. Note that this try with resource does not allow you to write like this:

// 错误的
try{
    
    
}with{
    
    
}resource{
    
    
}

It literally means that resources are included in try:
The parentheses followed by try declare the resources that need to be closed. The correct way of writing is:

   try( FileInputStream fileInputStream = new FileInputStream("xxx")) {
    
    

            System.out.println("业务逻辑");
        } catch (FileNotFoundException e) {
    
    
            e.printStackTrace();
        }

In this way, fileInputStream will automatically call the close method, and this article is about how to call it automatically.

  • Added AutoCloseable interface after java7
  • Before java7, resource classes (such as InputStream, etc.) all implemented the Closeable interface, and after java7, Closeable inherited the AutoCloseable interface. Because it indirectly inherits the AutoCloseable interface, it can be called automatically
  • The automatic call is due to the invokedynamic instruction
    (this instruction is also used in lambda to funciton, such as People::getName, and it is also used when calling sacla Groovy Kotlin and other languages)

The invokedynamic instruction is usually used together with concepts such as method type (Method Type) and method handle (Method Handle) in the Java Virtual Machine Specification. The method type represents the parameter and return value types of the method, and the method handle represents the reference of the method.

At runtime, the JVM will dynamically select the method to be called based on the provided method handle and method type, and can generate new methods at runtime.

Method handle:

  • It is an important concept in the Java virtual machine. It can be regarded as a reference to a method, which is used to dynamically select the method to be called at runtime.

  • A method handle can be regarded as a lightweight function pointer, which contains information such as the name of the method, parameter types, and return value type.

  • In Java, method handles are represented by the java.lang.invoke.MethodHandle class.

  • Method handles can be used to call static methods, instance methods, construction methods, etc., and can also be used to get or set the value of a field.

  • Method handles are usually generated at compile time and can be stored in a constant pool.

  • At runtime, the method handle can be used to dynamically select the method to call, and any number and type of parameters can be passed.

  • The method handle can be used in conjunction with the invokedynamic instruction to implement some dynamic language features, such as functional programming and dynamic type checking, etc.

So the blogger boldly speculates that the handle method can replace reflection to a certain extent in the scene of dynamically calling the method

A certain T also answered that the handle method is lightweight and reflection is heavyweight.

First of all, using the reflective invoke() method to call a method is a more flexible way, and any class and any method can be called, but its performance is relatively low, because every time a method is called, certain runtime checks and analysis are required.

The calling performance of the method handle is relatively high, because the method handle has already determined the signature and location of the target method to be called at compile time, avoiding analysis and checking at runtime. In addition, method handles can be optimized and inlined by the compiler, further improving performance.

So, the blogger started to write a demo called by handle:

public class MethodHandleController {
    
    

    public static void test(Integer a, Integer b) {
    
    
        System.out.println("测试句柄====有参");
    }

    public static void main(String[] args) throws Throwable {
    
    
        // 有参调用 如 test(Integer a, Integer b);
        MethodHandles.Lookup lookup = MethodHandles.lookup();

//        MethodType mt = MethodType.methodType(void.class, Integer.class, Integer.class);
//        MethodHandle mh = lookup.findStatic(MethodHandleController.class, "test", mt);
        // 调用方法
//       mh.invokeExact(Integer.valueOf(1), Integer.valueOf(2));

        // 也可以换成通用的 需要用户传入返回类型,方法参数数量 以及按顺序传入方法的参数类型
        MethodType genericType = MethodType.genericMethodType(2);

        MethodType methodType = genericType.changeParameterType(0, Integer.class);
        MethodType methodType1 = methodType.changeParameterType(1, Integer.class);

        MethodType methodType2 = methodType1.changeReturnType(void.class);

        // findStatic : 查找静态方法句柄
        MethodHandle commonMh = lookup.findStatic(MethodHandleController.class, "test", methodType2);

        Object invoke = commonMh.invoke(1, 2);

        // 需要严格匹配参数及返回值 例如调用了一个 void方法 如果我们用了变量去接收返回值 则会报错 所以不推荐该方法 毫无容错率
        commonMh.invokeExact(Integer.valueOf(1), Integer.valueOf(2));
        System.out.println("==");


        // 无参调用  findVirtual: 查找实例方法句柄 且需要bindTo实例

        MethodType noArgMethodType = MethodType.methodType(void.class);
        MethodHandle mh = lookup.findVirtual(MethodHandleController.class, "testNoArg", noArgMethodType)
                .bindTo(lookup.findConstructor(MethodHandleController.class, noArgMethodType).invoke());

        // 使用 invoke() 方法调用无参方法句柄
        mh.invoke();

        // 使用 invokeExact() 方法调用无参方法句柄
        mh.invokeExact();
    }

    public void testNoArg() {
    
    
        System.out.println("测试句柄==非静态====无参");
    }

}

There are many details in this code (including the commented ones). The following paragraph must be read together with the code:

  • If the above code is to be made universal, the user (user) needs to pass in the return type, the number of method parameters, and the type of each parameter passed into the method in order, and then in our for loop, use methodType.changeParameterType to continuously replace it. .
    It is worth noting that: MethodType.genericMethodType(num); num means that the method has several parameters, which is why the user needs to pass in the number of method parameters, and the object type is generated by default, so the parameter type needs to be passed in through changeParameterType method to replace

  • MethodType.genericMethodType(num);

  • The first parameter of the MethodType.methodType() method is the return value type of the method, if it is void, it is void.class

  • If the method is static and the method is known, use
    lookup.findStatic, if it is non-static, use lookup.findVirtual, and an instance of bindTo is required

  • invokeExact is an extremely strict method. When the method is known, that is, when it is not written as a general purpose, you can use this method to improve efficiency. It is not recommended to use it at other times.
    For example, our method is test(Integer a, Integer b), and when executing commonMh.invokeExact(1, 2); if an int type number is passed in, an error will be reported, and it will not automatically unbox and box ;
    Another example, our method is void test(), if a variable is declared before commonMh.invokeExact(Integer.valueOf(1), Integer.valueOf(2)); to
    receive the return value:
    Object res = commonMh.invokeExact( Integer.valueOf(1), Integer.valueOf(2)); Then this line of code will report an error.

    insert image description here
    Instead, we can use the invoke() method

The creation of this article is not easy, please declare the original source for reprinting, please do not repost directly

Guess you like

Origin blog.csdn.net/qq_36268103/article/details/130205252