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

news2024/11/26 22:32:10

最近在思考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块中进行初始化的,实现过程和上文示例中相同。
在这里插入图片描述

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

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

总结

简单写了下lambda代替反射的demo,之前几个项目里面有用到使用代理类来实现项目中的SPI实现,这种大量调用的情况如果在初始化过程中使用lambdaFactory,之后之间使用函数对象调用,能快不少,有空改写下试试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/647253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《离散数学》:逻辑

〇、前言 离散数学是数学的一个分支&#xff0c;研究离散对象和离散结构的数学理论和方法。这学期学校开了离散数学的课程&#xff0c;我受益颇丰&#xff0c;感觉到了离散数学真正的魅力&#xff0c;也被开创离散数学各个分支的人的聪明与才智深深折服。与连续数学不同&#…

Stopwatch工具类计时器探究

搬砖的我们 特别是Java开发的童鞋们, 经常需要通过记录开始时间和结束时间&#xff0c;然后通过计算差值&#xff0c;得到时间差&#xff0c;如下面的代码&#xff1a; long start System.currentTimeMillis(); long end System.currentTimeMillis(); System.out.println(…

【数字调制】数字调制技术FSK与PSK分析与研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

栈的数据结构完成表达式(5*10+2-7+5)/10+5的计算

栈&#xff08;Stack&#xff09;是一种线性数据结构&#xff0c;具有后进先出&#xff08;LIFO&#xff09;的特性。它可以理解为一种类似于抽屉的容器&#xff0c;只能在顶部进行插入和删除操作&#xff0c;其他位置不可访问。栈的基本操作包括入栈&#xff08;push&#xff…

[数字图像处理]第五章 图像复原与重建

文章目录 第五章 图像复原与重建5.1图像退化复原过程的模型图像退化图像复原图像复原与图像增强图像退化的数学模型为什么采用线性位移不变系统模型来描述图像退化过程 5.2 噪声模型x5.2.1 噪声的空间和频率特性5.2.2 一些重要的噪声概率密度函数高斯噪声实验&#xff1a;采用高…

【微服务架构设计和实现】4.1 微服务架构概述和设计原则

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;【容器化应用程序设计和开发】 第三章&#xff1a;【基于容器的部署、管理和扩展】 4.1 微服务架构概述和设计原则 4.1 微服务架构概述和设计原则4.1.1 微服务架构的优点4.1.2 微服务架构遵…

在 ArcGIS Pro 中使用 H3 创建蜂窝六边形

在 ArcGIS Pro 中使用 H3 创建蜂窝六边形https://mp.weixin.qq.com/s/tGk7AT2jAcvsmNyp2bRvig 之前看了个有意思的ArcGIS博客&#xff1a;H3六边形&#xff0c;当然了这也不是个新鲜东西了。原文&#xff1a; https://www.esri.com/arcgis-blog/products/arcgis-pro/analytic…

为什么要学GIS开发

什么是地理信息系统技术&#xff1f; GIS技术使用专门的计算机系统来获取地理数据并将其集成到智能“超级”地图中。然后&#xff0c;这些数据可用于创建无穷无尽的“假设”场景&#xff0c;为以下应用程序提供强大的工具&#xff1a; 制图&#xff08;地图制作&#xff09;应…

【阅读随笔】Rewrite-Based Decomposition of Signal Temporal Logic Specifications

文章目录 Overview1 IntroLTL任务分解STL任务分解本文工作 Background and Problem DefinitionSTLAgent假设与问题方法 An STL Rewriting SystemRewriting SystemFormula Rewrite DAG Decomposing STL智能体编队任务分解最优分解 Exploring the Formula Rewrite DAGExperiments…

soci在windows下vs2010编译

需要下载 的资源 mysql connector c 因为其使用的的是mysql connector c的api&#xff0c;需要下载https://downloads.mysql.com/archives/c-c/ 分别对应32位和64位的 soci 4.0 从github上下载4.03分支 https://github.com/SOCI/soci/tree/v4.0.3 cmake 需要下载3.25版…

Java多线程快速入门

文章目录 Java多线程快速入门1、认识多线程2、多线程的实现2.1 继承Thread类2.2 实现Runnable接口2.3 利用Callable和Futrue接口2.4 三种方式的比较 3、Thread类常用API3.1 守护线程3.2 礼让线程3.3 插入线程3.4 线程的生命周期 5、线程安全问题5.1 synchronized5.2 Lock 6、等…

wsl安装ubuntu并设置gnome图形界面详细步骤(win11+ubuntu18)

0.前言 wsl确实是个好东西&#xff0c;不过之前配了好几次都没有成功&#xff0c;因为wsl本身确实是有bug。当时配的时候查到GitHub上的一个issue还没被修好。现在重新配一下。 我的环境是Windows11家庭版。区别于win10&#xff0c;win11安装完默认就是wsl2。 1.下载 首先打…

Linux之管理联网

目录 Linux之管理联网 rhel8与7的区别 NetworkManager 定义 NM能管理各种网络 使用方法 使用NM的原因 nmcli使用方法 nmcli的两个常用命令 nmcli connection 定义 两种状态 nmcli device 定义 四种状态 nmcli常用命令 查看ip&#xff08;类似于ifconfig、ip addr&a…

每周练习学习(一)1.1--nc的使用与系统命令执行

1.test_your_nc ----nc的使用与系统命令执行1 平台&#xff1a;buuctf 之前在攻防开学来考核的时候&#xff0c;遇到过一个nc的题&#xff0c;但自己完全不知道nc是什么意思&#xff0c;所以现在为了增强一点自己的知识面&#xff08;说的好听&#xff0c;也就是为了快期末…

4.8 Socket介绍 4.9字节序 4.10字节序转换函数

4.8 Socket介绍 所谓 socket&#xff08;套接字&#xff09;&#xff0c;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲&#xff0c;套接字上…

使用EasyCode自定义模板,自动生成代码

首先创建spring boot项目&#xff0c;导入相关依赖是必须的#### 导入依赖&#xff1a; pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001…

设计模式(十八):行为型之观察者模式

设计模式系列文章 设计模式(一)&#xff1a;创建型之单例模式 设计模式(二、三)&#xff1a;创建型之工厂方法和抽象工厂模式 设计模式(四)&#xff1a;创建型之原型模式 设计模式(五)&#xff1a;创建型之建造者模式 设计模式(六)&#xff1a;结构型之代理模式 设计模式…

【RabbitMQ教程】第六章 —— RabbitMQ - 延迟队列

&#x1f4a7; 【 R a b b i t M Q 教程】第六章—— R a b b i t M Q − 延迟队列 \color{#FF1493}{【RabbitMQ教程】第六章 —— RabbitMQ - 延迟队列} 【RabbitMQ教程】第六章——RabbitMQ−延迟队列&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人…

数据提取概述

数据提取概述 一、响应内容的分类 在发送请求获取响应之后&#xff0c;可能存在多种不同类型的响应内容&#xff1b;而且很多时候&#xff0c;我们只需要响应内容中的一部分数据 结构化的响应内容 json字符串 可以使用re、json等模块来提取特定数据json字符串的例子如下图 x…

一.《UE5夜鸦》被动技能名字CALL和描述CALL

被动技能名字描述CALL 搜索名字寻找名字库的名字对象 1.搜索我们找名字,肯定是需要用CE搜索名字拉,由于是韩文,我们用翻译器截图获取韩文字符串 2.开始截图获取 3.我们用CE搜索字符串,这里注意是UTF-16勾上,找到了4个完全一样的结果, 我们修改确认哪一个才是真正技能库的名字 4…