利用lambda优化反射功能实现方法调用

这篇具有很好参考价值的文章主要介绍了利用lambda优化反射功能实现方法调用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近在思考lambda相关的问题,简单记录下做的相关反射替代和函数映射的尝试。

原理分析

lambda是jdk8才提供的,原理其实就是动态生成内部类来执行函数映射的方法。也就是说一段lambda表达式会对应特定的类方法,之后调用。底层是通过LambdaMetaFactory实现的函数映射,利用了jdk7给出的MethodHandler之类的函数式编程相关类来实现对函数的映射,MethodType用于函数签名。
反射的话基本接触过java的都听说过,底层实现是利用了native的invoke方法,因此说大量使用反射会影响性能,毕竟调用native方法开销会更大一点。
那么,只要利用LambdaMetaFactory或者函数式编程语法糖来获取对应的方法,并在类初始化的情况下进行构建,那么之后利用该对象调用对应方法,就会和直接调用原始方法一样快,因为本质上是在调用生成的内部类的方法。

简单尝试

首先是直接使用反射的方法,对一个User类对象获取其中的Method,invoke直接调用。
反射大家都会,直接给出实现。
用户类,包括函数编程接口User和实现类

@FunctionalInterface
public interface User {
    String getId();
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SimpleUser implements User{
    String id;
    String name;
}
反射类实现
// 简单反射调用user中方法
public class UserReflectAdaptor {
    public static String reflectUserId(SimpleUser user) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<? extends SimpleUser> c = user.getClass();
        Method method = c.getMethod("getId");
        return (String) method.invoke(user);
    }

    public static void main(String[] args){
        SimpleUser user = new SimpleUser("123", "mage");
        try {
            System.out.println(reflectUserId(user));
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

简单获取Method对象,并使用invoke方法实现代理调用。

lambda表达式实现

lambda表达式进行函数映射基本有数种方法,比较简单的是直接利用::获取函数对象

static User user;
@Benchmark
public static String lambdaUserId() {
    Supplier<String> idGetter = user::getId;
    return idGetter.get();
}

@Benchmark是用来做jmh test的,SupplierFunction的一种实现类,用来表示只有输出没有输入的方法对象。直接使用get()即可调用对应的方法获取结果。

已有对象,需要对特定对象进行bind的情况

需要调用的是public方法,同时有已存在的对象的情况下可使用该方式。
使用返回方法对象的方式:

    @Benchmark
    public static String lambdaFactoryInstanceUserId(){
        User sampleUser;
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            Method method = User.class.getMethod("getId");
            // lookup对应的函数,非反射直接映射
            MethodHandle methodHandle = lookup.unreflect(method);
            // 构建函数映射,invokeType为callSite的期望输出,必须为接口类型
            // 增加参数用来输入实现类
            MethodType invokedType = MethodType.methodType(User.class, User.class);
            // 方法对应Type,参数为输出和输入
            MethodType returnType = MethodType.methodType(String.class);
            // 句柄指向真正的方法
            CallSite callSite = LambdaMetafactory.metafactory(lookup, "getId",
                    invokedType,
                    returnType,
                    methodHandle,
                    returnType);
            // 参数表示实现类对象
            sampleUser = (User) callSite.getTarget().invokeExact(user);
            return sampleUser.getId() + " lambda user interface";
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

需要注意的是,示例里名为user的对象为已有的对象,要对其进行绑定并调用getId方法获取对应的结果。
首先和反射相似,获取对应的method对象。lookup.unreflect方法将对应method设为不可反射,以此获取方法签名和控制MethodHandler。之后使用LambdaMetafactory.metafactory工厂方法来构建对应的lambda对象生成类。方法参数可解释为:

  1. lookup,即为获取调用method对象的lookup类,在之前已初始化。
  2. 方法名,即为之后要调用的方法的名称,在这个示例生成的是User类,所以直接写对应的要调用方法的名称即可。
  3. invokeType,这个参数要注意的是,这个Type对应的是factory生成的对象类型,比如这个示例里生成User接口对应的对象。这里第二个参数指的是callSite.getTarget().invokeExact(user);中绑定的User对象参数。
  4. returnType这个Type对应的是字节码bytecode层面的调用方法的对应,也就是getId方法对应的MethodType。因为是字节码层面的对应,这里也可写成MethodType.methodType(Object.class)
  5. MethodHandle前面已经初始化,也是用来确定要调用的方法。
  6. 最后的returnType为真正调用时需要的,所以是String.class,也就是getId方法,只有返回值String而没有参数。
    最后,在invokeType中我们增加了User.class参数,因此在callSite.getTarget().invokeExact(user);中可以进行对象的绑定,实现lambda优化的方法调用。

不需要与特定对象进行bind的情况

比较灵活的方法是生成Function函数接口相关的对象,如BiCustom,supplier之类的,这个示例中直接生成Function。
设定一个简单工具类:

public class SimpleUserGetter implements UserGetter{
    @Override
    public String getId(User user) {
        return user.getId();
    }
    private String privateMethodGetter(){
        return "this is private";
    }
    public String publicMethodGetter(){
        return "this is public";
    }
}

对public方法publicMethodGetter方法进行lambda优化:

public static String lambdaFactoryFunction(){
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        try {
            Method method = SimpleUserGetter.class.getMethod("publicMethodGetter");
            // lookup对应的函数,非反射直接映射
            MethodHandle methodHandle = lookup.unreflect(method);
            // 构建函数映射,invokeType为callSite的期望输出,必须为接口类型
            MethodType invokedType = MethodType.methodType(Function.class);
            // 方法对应Type,参数为输出和输入
            MethodType returnType = MethodType.methodType(String.class, SimpleUserGetter.class);
            // 这边的apply对应的是Function的方法apply
            CallSite callSite = LambdaMetafactory.metafactory(lookup, "apply",
                    invokedType,
                    // byteCode level
                    MethodType.methodType(Object.class, Object.class),
                    methodHandle,
                    returnType);
            Function sp = (Function) callSite.getTarget().invokeExact();
            return (String)sp.apply(simpleUserGetter);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return "error";
    }

这边需要注意的变化是invokedType变化了,参数为Function.class,并只有一个,因为并不需要绑定特定的对象即可生成Function接口实现类对象。
另一点是方法名,这边是apply,因为这个方法名参数对应的是最终调用的方法,也就是Function接口中apply方法,而不是getId,这是非常容易错的。
因此最后的returnType也进行了变化,需要贴合apply方法进行设置。

获取private方法的情况

调用privateMethodGetter方法:

    public static String lambdaFactoryPrivateFunction(){
        try {
            MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(SimpleUserGetter.class, MethodHandles.lookup());
            Method method = SimpleUserGetter.class.getDeclaredMethod("privateMethodGetter");
            method.setAccessible(true);
            // lookup对应的函数,非反射直接映射
            MethodHandle methodHandle = lookup.unreflect(method);
            // 构建函数映射,invokeType为callSite的期望输出,必须为接口类型
            MethodType invokedType = MethodType.methodType(Function.class);
            // 方法对应Type,参数为输出和输入
            MethodType returnType = MethodType.methodType(String.class, SimpleUserGetter.class);
            CallSite callSite = LambdaMetafactory.metafactory(lookup, "apply",
                    invokedType,
                    MethodType.methodType(Object.class, Object.class),
                    methodHandle,
                    returnType);
            Function sp = (Function) callSite.getTarget().invokeExact();
            return (String)sp.apply(simpleUserGetter);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return "error";
    }

最重要的是lookup的初始化,需要直接给予private方法调用的权限,使用MethodHandles.privateLookupIn来获取对SimpleUserGetter的private lookup权限。
Method的accessible权限获取大家都懂,不赘述了。

时间测试

简单对使用lambda优化对象进行调用和每次都反射调用的方法进行测试。
对三个方法进行测试:

    @Benchmark
    public static String SimpleUserId() {
        return user.getId();
    }

    // static方法,利用额外的类进行user处理
    @Benchmark
    public static String reflectUserId() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<? extends SimpleUser> c = (Class<? extends SimpleUser>) user.getClass();
        Method method = c.getMethod("getId");
        return (String) method.invoke(user);
}

    @Benchmark
    public static String lambdaFactoryUserId(){
        return simpleUser.getId();
    }

分别为直接调用,反射方法和创建lambda对象后进行调用,其中最后一个方法的simpleUser是在类的static块中进行初始化的,实现过程和上文示例中相同。
利用lambda优化反射功能实现方法调用

方法 时间
直接调用 1 0 − 9 10^{-9} 109
利用反射 1 0 − 7 10^{-7} 107
利用lambda 1 0 − 9 10^{-9} 109

最后的时间结果可以看出,lambda优化的方法代理调用不需要额外的开销。

总结

简单写了下lambda代替反射的demo,之前几个项目里面有用到使用代理类来实现项目中的SPI实现,这种大量调用的情况如果在初始化过程中使用lambdaFactory,之后之间使用函数对象调用,能快不少,有空改写下试试。文章来源地址https://www.toymoban.com/news/detail-484824.html

到了这里,关于利用lambda优化反射功能实现方法调用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java】反射简介,利用反射打印一个类当中的构造函数,方法和属性。

       📝个人主页:哈__ 期待您的关注  我想要通过反射来打印如下效果的类信息。 Student类如下代码所示。  你是否有思路?如果你不了解反射的话,我来给大家简单的介绍一下反射的使用方法。 1、Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操

    2024年04月10日
    浏览(33)
  • 仿`gRPC`功能实现像调用本地方法一样调用其他服务器方法

    在介绍 gRPC 简介之前我们先了解一写概念: 单体架构 单体架构简单理解就是所有的业务代码都在一台服务器上,一旦某个服务宕机,会引起整个应用不可用,隔离性差。只能整体应用进行伸缩,例如整体打包部署一台或多台服务器,浪费资源,可伸缩性差。代码耦合在一起

    2024年02月09日
    浏览(32)
  • C# 通过反射调用对象上的方法

    2024年02月13日
    浏览(38)
  • 使用反射调用类的私有内部类的私有方法

    在进行单元测试时,我们往往需要直接访问某个类的内部类或者某个类的私有方法,此时正常的调用就无能为力了,因此我们可以使用反射进行调用。 类 实现方法 获取类的Class对象 Class class = Class.forName(\\\"类名\\\") 或 Class class = 类实例.getClass() new一个实例 Object object = class.newIn

    2024年02月07日
    浏览(30)
  • 加载AB包程序集,反射获取并实例化类调用方法

     加载AB包文件,加载bytes程序集资源,通过反射获取类,实例化添加组件,调用方法

    2024年02月13日
    浏览(30)
  • 即然利用反射机制可以破坏单例模式,有什么方法避免呢?

    私有构造方法中添加防止多次实例化的逻辑:在单例类的私有构造方法中,可以添加逻辑来检查是否已经存在实例,如果存在则抛出异常或返回已有的实例。这样即使通过反射创建了新的实例,也能在构造方法中进行拦截。 使用枚举实现单例:枚举类型的实例是唯一的,且在

    2024年02月13日
    浏览(40)
  • 【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)

    💥💥💞💞 欢迎来到本博客 ❤️❤️💥💥 🏆博主优势: 🌞🌞🌞 博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️ 座右铭: 行百里者,半于九十。 📋📋📋 本文目录如下: 🎁🎁🎁 目录 💥1 概述 📚2 运行结果 2.1 CCGKKT 2.2  CCGSD 2.3  SPKKT 2.4 SDSP 2.5 

    2023年04月15日
    浏览(44)
  • Java反射调用jar包实现多态

    上一篇实现了反射调用jar包,但是没有实现多态,这次先给自己的jar包类抽象一个接口,然后实现类实现接口。最后调用放反射得到的对像转换成接口类型调用执行。 定义接口,指定包为ZLZJar 实现接口方法 生成jar包 生成jar包指定id和版本,引用要一致 把jar包拷贝到调用方工

    2024年02月07日
    浏览(29)
  • C++ lambda表达式函数递归调用简单写法实现

    在C++11中,lambda表达式函数递归往往会带上 functional 头文件。书写形式如下: 还有相对简单点的第二种写法(C++14): 对于第二种, auto fib 的作用是为了在 lambda 表达式内部能够递归调用自身。在 C++14 中,lambda 表达式默认是无法直接递归调用的,因为在 lambda 内部无法访问到

    2024年02月15日
    浏览(36)
  • 反射、枚举和lambda表达式

    Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任 意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信 息以及动态调用对象方法的功能称为java语言的反射

    2024年01月21日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包