当前位置: 代码网 > it编程>编程语言>Java > Java使用MethodHandle来替代反射,提高性能问题

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

2025年05月29日 Java 我要评论
一、认识methodhandle1、简介java从最初发布时就支持反射,通过反射可以在运行时获取类型信息,但其有个缺点就是执行速度较慢。于是从java 7开始提供了另一套api methodhandl

一、认识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 | illegalaccessexception 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", string.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, classloader loader) 

// 生成一个 接受int、long和string类型的参数,返回一个string类型
methodtype.frommethoddescriptorstring("(ijljava/lang/string;)ljava/lang/string;", null); 

(2)修改methodtype

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

// 在方法后面追加多个参数
methodtype appendparametertypes(list<class<?>> ptypestoinsert) 
// 在方法后买你追加一个参数
methodtype 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(),因为它避免了在运行时进行类型适配的开销。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com