【java】深入分析try with resouce的原理 java如何通过方法句柄的方式进行方法调用

这个代码如果是复制用去,非常简单,但是推导过程真的特别艰辛,十几二十行的代码 博主和某T吵了一下午,到最后某T给的写法还是错误的,总之博主能写出这个demo实属不易。推荐看到本博客的同学自己再去深入学习相关的内容,毕竟某T都犯错的领域的知识点,并不是那么容易掌握的,出错我们得知道排查。

前言:博主为什么会知道方法句柄这个概念呢?

我们知道在jdk7中,引入了 try with resource的用法,用来一定程度上替代try catch finally, 注意 这个try with resource不是让你这么写:

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

它是字面意思,try中包含资源 :
try跟着的小括号里面 声明需要关闭的资源,正确写法:

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

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

这样 fileInputStream 就会自动调用close方法了,本文就是讲述如何自动调用的。

  • 在java7 之后 新增了 AutoCloseable 接口
  • 在java7之前 资源类(如 InputStream 等),都是实现了 Closeable 接口, 而在java7之后 Closeable又继承了 AutoCloseable 接口, 因为间接继承了AutoCloseable接口 所以可以自动调用
  • 自动调用是因为 invokedynamic 指令
    (在 lambda 转funciton中 例如 People::getName 也用到该指令 此外调用sacla Groovy Kotlin等语言时 也会用到该指令)

invokedynamic指令通常与Java虚拟机规范中的方法类型(Method Type)和方法句柄(Method Handle)等概念一起使用。方法类型表示方法的参数和返回值类型,方法句柄表示方法的引用。

在运行时,JVM会根据提供的方法句柄和方法类型动态地选择需要调用的方法,并且可以在运行时生成新的方法。

方法句柄:

  • 是Java虚拟机中一个重要的概念,它可以看作是一个方法的引用,用于在运行时动态地选择需要调用的方法。

  • 可以将方法句柄视为一个轻量级的函数指针,它包含了方法的名称、参数类型和返回值类型等信息。

  • 在Java中,方法句柄是通过java.lang.invoke.MethodHandle类来表示的。

  • 方法句柄可以用来调用静态方法、实例方法、构造方法等,也可以用来获取字段的值或者设置字段的值。

  • 方法句柄通常是在编译时生成的,并且可以存储在常量池中。

  • 在运行时,可以使用方法句柄来动态地选择需要调用的方法,并且可以传递任意数量和类型的参数。

  • 方法句柄可以与invokedynamic指令配合使用,实现一些动态语言的特性,例如函数式编程和动态类型检查等

所以博主大胆推测:在动态调用方法的场景中 句柄的方式可以一定程度上代替反射

某T也回答 句柄方式是轻量级的 而反射是重量级的。

首先,使用反射 invoke() 方法调用方法是一种比较灵活的方式,可以调用任何类和任何方法,但是它的性能相对较低,因为每次调用方法都需要进行一定的运行时检查和解析。

而方法句柄的调用性能相对较高,因为方法句柄是在编译期就已经确定了调用的目标方法的签名和位置,避免了运行时的解析和检查。此外,方法句柄还可以被编译器优化和内联,进一步提高了性能。

于是,博主开始写一个句柄方式调用的demo:

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("测试句柄==非静态====无参");
    }

}

这段代码中(包括被注释的)有很多细节 下面这段话 一定需要结合代码一起看:

  • 如果上述代码要做成通用的, 需要用户(使用者)传入返回类型,方法参数数量 以及按顺序传入方法的每个参数类型,然后我们for循环中,用methodType.changeParameterType 不断替换就好了。
    值得注意的是:MethodType.genericMethodType(num); num代表方法有几个参数,这也是为什么需要使用者传入方法参数数量的原因,而且默认生成的是Object类型,所以需要传入参数类型 通过 changeParameterType 方法进行替换

  • MethodType.genericMethodType(num);

  • MethodType.methodType() 方法的第一个参数 是方法的返回值类型,如果是void, 则为void.class

  • 如果方法是静态的,且方法已知,则使用
    lookup.findStatic,如果是非静态的 则使用lookup.findVirtual,且需要bindTo 一个实例

  • invokeExact 是一个极其严格的方法,在方法已知的时候,也就是不写成通用时,可以用这个方法提高效率,其它时候一律不推荐使用。
    例如我们的方法是 test(Integer a, Integer b),而执行时 commonMh.invokeExact(1, 2); 传入的是int类型的数字,就会报错了,它并不会自动的拆箱装箱;
    再例如,我们的方法是void test(), 如果在 commonMh.invokeExact(Integer.valueOf(1), Integer.valueOf(2));
    前面声明一个变量 去接收返回值:
    Object res = commonMh.invokeExact(Integer.valueOf(1), Integer.valueOf(2)); 那这行代码将会报错。

    在这里插入图片描述
    取而代之的是,我们可以使用invoke()方法

本文创作不易,转载请声明原出处,请勿直接搬运

猜你喜欢

转载自blog.csdn.net/qq_36268103/article/details/130205252