Java使用MethodHandle来替代反射,提高性能问题

2025-05-30 03:50

本文主要是介绍Java使用MethodHandle来替代反射,提高性能问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑...

一、认识MethodHandle

1、简介

Java从最初发布时就支持反射,通过反射可以在运行时获取类型信息,但其有个缺点就是执行速度较慢

于是从Java 7开始提供了另一套API MethodHandle 。其与反射的作用类似,可以在运行时访问类型信息,但是其执行效率比反射更高(性能几乎接近方法调用)

MethodHandle:是java.lang.invoke.MethodHandle的一个实例,它是对Java中某个方法(包括实例方法、静态方法、构造函数等)的直接可执行引用。

  • 与传统的Java反射相比,MethodHandle更加轻量级和高效,因为它绕过了许多反射的额外开销,如访问控制检查等。
  • MethodHandle是对方法的直接引用,可以直接通过MethodHandle对象调用目标方法,无需像反射那样先获取Method对象。
  • MethodHandle具有类型检查的特性,在编译时会检查MethodHandle的类型与目标方法的类型是否匹配。

2、使用方式

1、创建MethodHandle对象:

  • 使用MethodHandles.Lookup类的lookup()方法获取一个MethodHandles.Lookup对象。
  • 使用MethodHandles.Lookup对象的findStatic(), findVirtual(), findSpecial(), findConstructor()等方法来查找并获取目标方法的MethodHandle对象。

2、绑定MethodHandle到目标方法(如果需要):

  • 如果MethodHandle指向的是实例方法,可以使用MethodHandle对象的bindTo()方法将其绑定到目标实例上。

3、调用目标方法:

  • 使用MethodHandle对象的invoke()、invokeExact()、invokeWithArguments()等方法来调用目标方法。

3、与反射的区别

  • 性能:MethodHandle通常比反射更快,因为它绕过了许多反射的额外开销。
  • 类型安全:MethodHandle在编译时会进行类型检查,而反射在运行时进行类型检查,可能导致ClassCastException等异常。
  • 用法:反射需要先获取Method对象,而MethodHandle直接对方法进行引用。

Method Handles比反射更难用,因为没有列举类中成员,获取属性访问标志之类的机制。

二、示例

1、基本使用

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

class Calculator {
    public String add(int a, int b) {
        return String.valueOf(a + b);
    }
}

public class MethodHandleExample {

    public static void main(String[] args) throws Throwable {
        try {
            // 获取Calculator类的方法句柄
            // 注意:由于我们是在Class内部查找,所以可以使用MethodHandles.lookup()
            // 在实际应用中,如果Class是其他包中的类,你可能需要不同的Lookup实例
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            // 获取方法,第一个参数为返回值,剩余的参数为方法参数
            MethodType methodType = MethodType.methodType(String.class, int.class, int.class);
            // 通过方法名 查找某个类的某个方法
            MethodHandle addHandle = lookup.findVirtual(Calculator.class, "add", methodType);

            // 创建Calculator的实例
            Calculator calculator = new Calculator();
            // 直接调用方法,第一个参数为对象实例,剩余的参数为方法
            String result = (String) addHandle.invoke(calculator, 1, 2);
            System.out.println(result);
        } catch (NoSuchMethodException | IllegalAcwww.chinasem.cncessException e) {
            throw new RuntimeException(e);
        }

    }
}

2、(重要)提升性能的关键

使用MethodHandle来提升性能,一般是定义static final在启动时就加载好了,这样不用在运行时重新查找。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class MethodHandleExample {
    // 要static final类型的才能提升效率
    private static final MethodHandle ADD_HANDLE;

    static {
        // static final 修饰的 MethodHandle 可以在类加载时就完成初始化,避免了每次调用时的查找开销。
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodType methodType = MethodType.methodType(int.class, int.class, int.class);
            ADD_HANDLE = lookup.findVirtual(Calculator.class, "add", methodType);
        } catch (NoSuchMethodException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Throwable {
        Calculator calculator = new Calculator();
        int result = (int) ADD_HANDLE.invoke(calculator, 1, 2);
        System.out.println(result);
    }
}

3、实际使用

//无参数构造器
MethodType con1Mt = MethodType.methodType(void.class);
MethodHandle con1Mh = lookup.findConstructor(HandleTarget.class, con1Mt);
Object target1 = con1Mh.invoke();

//有参数构造器
MethodType con2Mt = MethodType.methodType(void.class, String.class);
MethodHandle con2Mh = lookup.findConstructor(HandleTarget.class, con2Mt);
Object target2 = con2Mh.invoke("ErGouWang");

//调用非private实例方法
MethodType getterMt = MethodType.methodType(String.class);
MethodHandle getterMh = lookup.findVirtual(HandleTarget.class, "getName", getterMt);
String name = (String) getterMh.invoke(target2);
System.out.println(name);

//访问private方法
Method learnMethod = HandleTarget.class.getDeclaredMethod("learnPrograming", Stringphp.class);
learnMethod.setAccessible(true);
MethodHandle learnProMh = lookup.unreflect(learnMethod);
learnProMh.invoke(target1, "Java");

//访问非private属性
MethodHandle nameMh= lookup.findGetter(HandleTarget.class,"name", String.class);
System.out.println((String) nameMh.invoke(con1Mh.invoke()));

//访问private的属性,需要借助反射
Field nameField = HandleTarget.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle nameFromRefMh = lookup.unreflectGetter(nameField);
System.out.println((String) nameFromRefMh.invoke(target1));

//访问private的属性,需要借助反射
Field nameField = HandleTarget.class.getDeclaredField("name");
nameField.setAccessible(true);
MethodHandle nameFromRefMh = lookup.unreflectGetter(nameField);
System.out.println((String) nameFromRefMh.invoke(target1));

三、源码分析

1、创建Lookup

// 以这种方式得到的lookup很强大,凡是调用类支持的字节码操作,它都支持
MethodHandles.Lookup lookup = MethodHandles.lookup();

// 以此种方式创建的lookup能力是受限的,其只能访问类中public的成员。
MethodHandles.Lookup publicLookup=MethodHandles.publicLookup();

2、MethodType

(1)创建MethodType

// MethodType使用其静态方法创建
// 获取方法,第一个参数为返回值,剩余的参数为方法参数

// 该类能够产生对方法的描述即 (Ljava/lang/Object;)V 该方法接收一个Object类型的值,没有返回值
// 其实该类就是用来对方法的描述,描述这个方法接受什么参数,返回什么类型的值
MethodType methodType(Class<?> rtype, Class<?> ptype0, Class<?>... ptypes)

MethodType genericMethodType(int objectArgCount, boolean finalArray) 

MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader)


想要获得MethodType对象,MethodType类提供了三个静态方法,如上所述。

// 第一个参数代表返回类型,如果没有则指定void.class即可,后面的参数都是这个方法接收的参数类型,可以有多个,也可以没有,MethodType.methodType()有多个重载方法
MethodType.methodType(Class<?>, Class<?>, Class<?>...) 

// 生成一个MethodType,第一个参数表示要生成的参数个数,并且都是Object类型,第二个参数表示是否要在最后再添加一个Object类型的数组,注意是添加哦
MethodType genericMethodType(int objectArgCount, boolean finalArray) 

// 从方法描述符来生成一个MethodType, 第二个参数为一个类加载器,如果为null则使用系统类加载器
MethodType fromMethodDescriptorString(String descriptor, Clashttp://www.chinasem.cnsLoader loader) 

// 生成一个 接受int、long和String类型的参数,返回一个String类型
MethodType.fromMethodDescriptorString("(IJLjava/lang/String;)Ljava/lang/String;", null); 

(2)修改MethodType

获得一个具体MethodType实例后,我们可以对它进行一些修改,比如更改参数类型,添加一个参数,删除一个参数等等,但是由于MethodType本身是不可变的,所以每对其进行一次更改都会产生一个新的MethodType对象​​​​​

// 在方法后面追加多个参数
MethodType appendParameterTypes(List<Class<?>> ptypesToInsert) 
// 在方法后买你追加一个参数
MethodTypeChina编程 appendParameterTypes(Class<?>... ptypesToInsert)
// 在指定参数位置插入一个参数 从 0开始
MethodType insertParameterTypes(int num, Class<?>... ptypesToInsert)
// 在指定参数位置插入多个参数 从 0开始
MethodType insertParameterTypes(int num, List<Class<?>> ptypesToInsert)
// 改变返回值类型
MethodType changeReturnType(Class<?> nrtype)
// 改变指定参数位置的参数类型
MethodType changeParameterType(int num, Class<?> nptype)
// 把基本类型变成对应的包装类型 (装箱)
MethodType wrap()
// 把包装类型变成对应的基本类型(拆箱)
MethodType unwrap()
// 把所有引用类型的参数变为Object类型
MethodType erase()
// 把所有参数都变成Object类型
MethodType generic()
// 构造出一个 (int,long,String)String
MethodType methodType = MethodType.fromMethodDescriptorString("(IJLjava/lang/String;)Ljava/lang/String;", null);
// (double, long, String)String
methodType = methodType.changeParameterType(0, Double.TYPE);
// (double, long, String, Object)String
methodType = methodType.appendParameterTypes(Object.class);
// (boolean, double, long, String, Object)String
methodType = methodType.insertParameterTypes(0, Boolean.TYPE);
// (float, double, long, String, Object)String
methodType = methodType.changeParameterType(0, Float.TYPE);
// (float, double, long, String, Object)Object
methodType = methodType.changeReturnType(Object.class);
// (Float, Double, Long, String, Object)Object
methodType = methodType.wrap();
// (float, double, long, String, Object)Object
methodType = methodType.unwrap();
// (float, double, long, Object, Object)Object
methodType = methodType.erase();
// (Object, Object, Object, Object, Object)Object
methodType = methodType.generic();

List<Class<?>> classList =  methodType.parameterList();
for (Class<?> clazz : classList) {
    System.out.println(clazz.getName());
}

        

3、创建MethodHandle

(1)MethodHandle (方法句柄)

// 所有方法都会查找指定类中的指定方法,如果查找到了
// 则会返回这个方法的方法句柄

// 返回指定类的指定构造函数
public MethodHandle findConstructor(Class<?> refc,
                                    MethodType type);
 
// 查找虚方法 final修饰的也可找到  
// 可以简单理解为虚方法指的是可以被子类覆盖的方法                                
public MethodHandle findVirtual(Class<?> refc,
                                String name,
                                MethodType type);
// 通过反射获取方法句柄
public MethodHandle unreflect(Method m);

// 查找静态方法
public MethodHandle findStatic(Class<?> refc,
                               String name,
                               MethodType type);

// 查找某个字段,并生成get方法的方法句柄(类中不需要存在这个字段的get方法)
ublic MethodHandle findGetter(Class<?> refc,
                               String name,
                               Class<?> type)

// 查找某个字段,并生成set方法的方法句柄(类中不需要存在这个字段的set方法)
public MethodHandle findSetter(Class<?> refc,
                  编程             String name,
                               Class<?> type)

(2)MethodHandles中静态工厂方法创建通用的方法句柄

// 1、用来创建操作数组的方法句柄
//MethodHandle arrayElementGetter(Class<?> arrayClass)
//MethodHandle arrayElementSetter(Class<?> arrayClass)

int[] arr = new int[]{1, 2, 3, 4, 5, 6};

MethodHandle getter = MethodHandles.arrayElementGetter(int[].class);
MethodHandle setter = MethodHandles.arrayElementSetter(int[].class);
setter.bindTo(arr).invoke(2, 50);
System.out.println(getter.bindTo(arr).invoke(2));
System.out.println(Arrays.toString(arr));

// 2、MethodHandle identity(Class<?> type)
// 该方法总是返回你给定的值,即你传的参数是什么就返回什么
MethodHandle identity = MethodHandles.identity(String.class);
System.out.println(identity.invoke("hello world"));
        
// 3、MethodHandle constant(Class<?> type, Object value)
//与上面那个方法不同的是,该方法在创建方法句柄的时候就指定一个值,然后每次调用这个方法句柄的时候都会返回这个值
MethodHandle helloWorld = MethodHandles.constant(String.class, "hello world");
System.out.println(helloWorld.invoke());


// 4、MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes)
// 可以简单理解为在调用的时候忽略掉哪些位置上的参数

public static void main(String[] args) throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
    // 忽略第0个参数并且类型为int类型的参数
    methodHandle = MethodHandles.dropArguments(methodHandle, 0, int.class);
    // 实际传递的只有3 
    methodHandle.invoke(2, 3);
}


public static int test(int i) {
    System.out.println(i);
    return 3;
}

// 5、MethodHandle filterArguments(MethodHandle target, int pos, MethodHandle... filters)
// 可以在方法调用的时候对参数进行处理
// 下面这个例子,test方法接收的是一个int类型的参数,但是我们传递的是一个字符串。因此我们把参数进行了一个处理 test = MethodHandles.filterArguments(test, 0, length);这行代码就是表示,test方法句柄调用的时候调用length方法句柄进行处理。

public static void main(String[] args) throws Throwable {

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle length = lookup.findVirtual(String.class, "length", MethodType.methodType(int.class));
    MethodHandle test = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
    test = MethodHandles.filterArguments(test, 0, length);
    // test()方法实际接收到的参数是5
    test.invoke("sdfsd");
}


public static int test(int i) {
    System.out.println(i);
    return 3;
}

// 6、MethodHandle insertArguments(MethodHandle target, int pos, Object... values)
// 给指定位置上的参数预先绑定一个值,这样在调用的时候就不能传了
public static void main(String[] args) throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class));
    // 预先给指定位置的参数绑定一个值 
    methodHandle = MethodHandles.insertArguments(methodHandle, 0, 22);
    // 由于参数i已经绑定值了,在这里调用的时候就不能传递参数了
    methodHandle.invoke();
}

public static int test(int i) {
    System.out.println(i);
    return 3;


// 7、MethodHandle foldArguments(MethodHandle target, MethodHandle combiner)
// 与上面的方法类似,不同的是该方法不是在指定位置绑定值,而是通过一个方法句柄的返回值,将该返回值放到最终在调用方法的前面

public static void main(String[] args) throws Throwable {

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
    MethodHandle max = lookup.findStatic(Math.class, "max", MethodType.methodType(int.class, int.class, int.class));
    methodHandle = MethodHandles.foldArguments(methodHandle, max);
    methodHandle.invoke(4, 5, 6);
}


public static int test(int i, int i2, int i3, int i4) {
    // 打印5
    System.out.println(i);
    return 3;
}


// 8、MethodHandle catchException(MethodHandle target, Class<? extends Throwable> exType, MethodHandle handler)
// 如果terget方法句柄出现了指定的异常或其指定的子类异常,则调用handler方法
public static void main(String[] args) throws Throwable {

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle methodHandle = lookup.findStatic(TestMain.class, "test", MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
    MethodHandle exceptionHandle = lookup.findStatic(TestMain.class, "handleException", MethodType.methodType(int.class, Exception.class, int.class, int.class, int.class, int.class));
    methodHandle = MethodHandles.catchException(methodHandle, Exception.class, exceptionHandle);
    methodHandle.invoke(4, 5, 6, 7);
}


public static int test(int i, int i2, int i3, int i4) {
    System.out.println(i);
    throw new RuntimeException("test出现异常");
}

public static int handleException( Exception e, int i, int i2, int i3, int i4) {
    System.out.println("handleException:\n" + e.getMessage());
    return 2;
}
值得注意的是 handleException方法的 异常类型参数只能在第一个位置,然后其他参数必须与出现异常方法的参数类型一致

// 9、MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exType)
// 构造出一个只抛出异常的方法句柄
MethodHandle handle = MethodHandles.throwException(String.class, Exception.class);
String invoke = (String) handle.invoke(new RuntimeException("throw"));



4、MethodHandle中的方法

// MethodHandle bindTo(Object x) 把一个对象与绑定并返回绑定后的方法句柄
MethodHandle bindTo(Object x)

/**
特点:
invoke()方法是MethodHandle的一个通用方法,它允许在调用时执行类型转换。
如果提供的参数类型与目标方法不匹配,invoke()会尝试使用MethodHandle的asType()方法进行参数适配。
参数:
invoke()方法接受一个可变参数列表(Object... args),其中第一个参数(如果目标方法是实例方法)是实例对象,后续参数是传递给目标方法的参数。
*/
Object	invoke(Object... args)

/**
特点:
invokeExact()方法提供了严格的类型检查。
如果提供的参数类型与目标方法的参数类型不匹配,invokeExact()将抛出WrongMethodTypeException。
参数:
invokeExact()同样接受一个可变参数列表(Object... args),但要求这些参数的类型必须与目标方法的参数类型完全匹配。
*/
Object	invokeExact(Object... args)

/**
特点:
invokeWithArguments()方法允许使用List<Object>作为参数列表进行调用。
这为调用者提供了一种更灵活的方式来构建参数列表,尤其是当参数数量不确定或需要动态构建时。
参数:
invokeWithArguments()接受一个List<Object>参数,其中第一个元素(如果目标方法是实例方法)是实例对象,后续元素是传递给目标方法的参数。
*/
Object	invokeWithArguments(Object... arguments)
  • invoke():提供类型适配的灵活调用,允许在运行时转换参数类型。
  • invokeExact():提供严格的类型检查,要求参数类型与目标方法完全匹配。
  • invokeWithArguments():允许使用列表作为参数进行调用,提供了更大的灵活性。

在选择使用哪种方法时,应该根据具体需求来决定。如果希望进行严格的类型检查,可以使用invokeExact();如果需要更灵活的参数传递方式,可以考虑使用invoke()或invokeWithArguments()。同时,需要注意的是,invokeExact()的性能通常优于invoke(),因为它避免了在运行时进行类型适配的开销。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程China编程(www.chinasem.cn)。

这篇关于Java使用MethodHandle来替代反射,提高性能问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1154851

相关文章

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Linux系统之stress-ng测压工具的使用

《Linux系统之stress-ng测压工具的使用》:本文主要介绍Linux系统之stress-ng测压工具的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、理论1.stress工具简介与安装2.语法及参数3.具体安装二、实验1.运行8 cpu, 4 fo

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

MySQL 事务的概念及ACID属性和使用详解

《MySQL事务的概念及ACID属性和使用详解》MySQL通过多线程实现存储工作,因此在并发访问场景中,事务确保了数据操作的一致性和可靠性,下面通过本文给大家介绍MySQL事务的概念及ACID属性和... 目录一、什么是事务二、事务的属性及使用2.1 事务的 ACID 属性2.2 为什么存在事务2.3 事务

eclipse如何运行springboot项目

《eclipse如何运行springboot项目》:本文主要介绍eclipse如何运行springboot项目问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目js录当在eclipse启动spring boot项目时出现问题解决办法1.通过cmd命令行2.在ecl

Java中的Closeable接口及常见问题

《Java中的Closeable接口及常见问题》Closeable是Java中的一个标记接口,用于表示可以被关闭的对象,它定义了一个标准的方法来释放对象占用的系统资源,下面给大家介绍Java中的Clo... 目录1. Closeable接口概述2. 主要用途3. 实现类4. 使用方法5. 实现自定义Clos

Jvm sandbox mock机制的实践过程

《Jvmsandboxmock机制的实践过程》:本文主要介绍Jvmsandboxmock机制的实践过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、背景二、定义一个损坏的钟1、 Springboot工程中创建一个Clock类2、 添加一个Controller

使用Python实现网页表格转换为markdown

《使用Python实现网页表格转换为markdown》在日常工作中,我们经常需要从网页上复制表格数据,并将其转换成Markdown格式,本文将使用Python编写一个网页表格转Markdown工具,需... 在日常工作中,我们经常需要从网页上复制表格数据,并将其转换成Markdown格式,以便在文档、邮件或