Java Lambda表达式原理解析

news2024/12/24 21:36:56

前言

java中有lambda,在使用java的过程中我们没少用(啥?用的kotlin?你别说话)但是你知道lambda的实现原理吗?

接下来就来解析lambda实现,不过在lambda之前我们与一个熟悉的老伙计谈谈心————匿名类,为什么因为他们有点类似.

匿名类的实现方式

从字节码的层面上来说new接口和new抽象类是极其抽象且不合理的。

比如这样。

public class Test {
​
    public static void main(String[] args) {
        new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello world");
            }
        }.run();
    }
​
}
​
复制代码

当我们对这个文件进行编译以后会得到两个文件

Test.class以及Test$1.class

使用jclasslib idea插件对Test.class文件解析以后会发现,我这里new了另外一个东西Test$1,这是个什么?

反编译以后就是这玩意

所以new接口和new抽象类这种方式并不会减少类的创建,只不过这个实现类是编译器在编译的时候自动帮助创建。而且这个匿名类的名称是有一定规律的。

小结

匿名类的实现即编译器静态生成一个类,new抽象的实现都是引用的具体的匿名类。

Lambda表达式实现

Lambda和匿名类的实现类似,他们的关系就类似于静态代理和动态代理。

匿名类是静态生成,而Lambda是动态生成。

何以见得?show code

public class Test {
​
    public static void main(String[] args) {
        Runnable run = () -> System.out.println("Hello world")
        run.run();
    }
​
}
复制代码

这种实现只能通过字节码进行分析。因为他是最直观的方式

这下还没点开就觉得有些猫腻了。hh,这也算是一个伏笔吧。

剧透一下

这边这个lambda是我们lambda内部的具体代码.

lambda实现实际是通过asm进行class的生成,然后这个生成的class内部的方法调用了lambdamainmainmain0.

main字节码分析

 0 invokedynamic #2 <run, BootstrapMethods #0>
 5 astore_1
 6 aload_1
 7 invokeinterface #3 <java/lang/Runnable.run : ()V> count 1
12 return
复制代码

短短几行代码,我却难以读懂...

invokedynamic

invokedynamic是关键。

他调用了常量池2号常量,也就是一个invokeDynamic

而这个常量链接了一个描述符和一个Bootstrap方法。

这个方法有些长啊

但是这是分析的关键

这是bootstrp方法的具体签名

<java/lang/invoke/LambdaMetafactory.metafactory :(Ljava/lang/invoke/MethodHandles$Lookup;
​
Ljava/lang/String;
​
Ljava/lang/invoke/MethodType;
​
Ljava/lang/invoke/MethodType;
​
Ljava/lang/invoke/MethodHandle;
​
Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;>
复制代码

等价于

 public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
复制代码

也就是说invokedynamic就相当于调用了这个方法

这代码呢也不长

 public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }
复制代码

就3行,第2行是一些参数的配置不太重要,第3行调用的方法是一个抽象。

所以这样来看InnerClassLambdaMetafactory是突破口

小细节

注意其static代码块

static {
    final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses";
    // 这里获取了一个System property。看这变量名称能猜出他是一个路径
    String dumpPath = GetPropertyAction.privilegedGetProperty(dumpProxyClassesKey);
    //如果dumpPath为null dumper就是null,否者会生成一个dumper
    dumper = (null == dumpPath) ? null : ProxyClassesDumper.getInstance(dumpPath);
    
    //这里也是做一个配置,不过作为过来人告诉你这个不是很重要。
    final String disableEagerInitializationKey = "jdk.internal.lambda.disableEagerInitialization";
    disableEagerInitialization = AccessController.doPrivileged(
        new GetBooleanAction(disableEagerInitializationKey)).booleanValue();
​
复制代码

构造函数

public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
                                   MethodType invokedType,
                                   String samMethodName,
                                   MethodType samMethodType,
                                   MethodHandle implMethod,
                                   MethodType instantiatedMethodType,
                                   boolean isSerializable,
                                   Class<?>[] markerInterfaces,
                                   MethodType[] additionalBridges)
        throws LambdaConversionException {
    //参数配置不重要
    super(caller, invokedType, samMethodName, samMethodType,
          implMethod, instantiatedMethodType,
          isSerializable, markerInterfaces, additionalBridges);
    implMethodClassName = implClass.getName().replace('.', '/');
    implMethodName = implInfo.getName();
    implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
    constructorType = invokedType.changeReturnType(Void.TYPE);
    lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
    //属于是核心逻辑了
    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    int parameterCount = invokedType.parameterCount();
    if (parameterCount > 0) {
        argNames = new String[parameterCount];
        argDescs = new String[parameterCount];
        for (int i = 0; i < parameterCount; i++) {
            argNames[i] = "arg$" + (i + 1);
            argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i));
        }
    } else {
        argNames = argDescs = EMPTY_STRING_ARRAY;
    }
}
复制代码

接着就是最核心的逻辑了

buildCallSite

CallSite buildCallSite() throws LambdaConversionException {
        final Class<?> innerClass = spinInnerClass();
    ...
    }
复制代码

第一行代码以后就生成了相应的class文件

spinInnerClass

代码挺长的

private Class<?> spinInnerClass() throws LambdaConversionException {
    String[] interfaces;
    String samIntf = samBase.getName().replace('.', '/');
    boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);
    //参数配置
    ...
​
    //利用asm ClassWriter直接生成class字节码
    cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
             lambdaClassName, null,
             JAVA_LANG_OBJECT, interfaces);
​
    // Generate final fields to be filled in by constructor
    for (int i = 0; i < argDescs.length; i++) {
        FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
                                        argNames[i],
                                        argDescs[i],
                                        null, null);
        fv.visitEnd();
    }
​
    generateConstructor();
​
    if (invokedType.parameterCount() != 0 || disableEagerInitialization) {
        generateFactory();
    }
​
    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
                                      samMethodType.toMethodDescriptorString(), null, null);
    mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
​
    if (additionalBridges != null) {
        for (MethodType mt : additionalBridges) {
            mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
                                mt.toMethodDescriptorString(), null, null);
            mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
            new ForwardingMethodGenerator(mv).generate(mt);
        }
    }
​
​
    ...
​
    cw.visitEnd();
    //转化为字节码
    final byte[] classBytes = cw.toByteArray();
​
    //如果dumper不为空就将class字节码文件输出到指定路径
    if (dumper != null) {
        AccessController.doPrivileged(new PrivilegedAction<>() {
            @Override
            public Void run() {
                dumper.dumpClass(lambdaClassName, classBytes);
                return null;
            }
        }, null,
        new FilePermission("<<ALL FILES>>", "read, write"),
        // createDirectories may need it
        new PropertyPermission("user.dir", "read"));
    }
​
    //定义class
    return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
}
复制代码

字节码分析

运行以后可以发现生成了一个class文件

然而这个class实现了对应的接口,并调用了生成的lambda方法

所以lambda表达式的实现原理就简单了,通过asm生成一个类动态地指向我们需要执行的代码块所对应的方法。

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

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

相关文章

长光卫星冲刺科创板上市,预计2025年底前实现300颗卫星在轨

近日&#xff0c;长光卫星技术股份有限公司&#xff08;下称“长光卫星”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。 本次冲刺科创板上市&#xff0c;长光卫星计划募资26.83亿元&#xff0c;将用于“吉林一号”卫星星座建设项目&#xff08;二期&a…

第五章:数据完整性

一、实体、参照、用户自定义完整性 1、【单选题】CREATE TABLE时定义属性上的约束条件&#xff0c;检查列值是否满足一个条件表达式的定义关键词为&#xff1a; 正确答案&#xff1a; A 2、【多选题】创建患者住院主记录表pat_visit&#xff0c;并定义主码{patient_id,visit_…

《图解TCP/IP》阅读笔记(第七章 7.1、7.2、7.3)—— 路由控制概念与路由控制算法

第七章 路由协议 本章旨在将详细介绍路由控制以及实现路由控制功能的相关协议 7.1 路由控制的定义 在互联网这片汪洋大海中&#xff0c;数据就好似一叶扁舟&#xff0c;没有灯塔的指引&#xff0c;是难以寻得目的地的。这种进行正确方向引导的转发数据的处理&#xff0c;就叫…

[LeetCode周赛复盘] 第 94 场双周赛20221225

[LeetCode周赛复盘] 第 94 场双周赛20221225 一、本周周赛总结二、 [Easy] 6273. 最多可以摧毁的敌人城堡数目1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6274. 奖励最顶尖的 K 名学生1. 题目描述2. 思路分析3. 代码实现四、[Medium] 6295. 最小化两个数组中的最大值1. 题…

从入门到项目实战 - Vue 键盘事件

Vue 中键盘事件的使用上一节&#xff1a;《 Vue 事件处理 》| 下一节&#xff1a;《 Vue 中鼠标事件的使用 》jcLee95 邮箱 &#xff1a;291148484163.com CSDN 主页&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 本文地址&#xff1a;https:/…

【TypeScript】TS泛型讲解及其使用

目录 泛型 泛型函数使用 泛型约束 多泛型使用 泛型接口 泛型类 泛型工具类型 泛型 泛型是可以在保证类型安全的前提下&#xff0c;让函数等与多种类型一起工作&#xff0c;从而实现复用&#xff0c;常用于&#xff1a;函数、接口、class中。日常我们创建的函数&#xf…

车牌检测模型训练(含源码和数据集)

车牌检测模型训练(含源码和数据集) 本教程利用NVIDIA TAO进行车牌检测模型的训练: 模型框架:SSD数据集: CRPD, 连接:https://github.com/yxgong0/CRPD训练框架: NVIDIA TAO, 安装教程连接: https://docs.nvidia.com/tao/tao-toolkit/text/tao_toolkit_quick_start_guide.html…

目标检测之YOLOv3算法分析

基本原理 特征网络 输入输出 输入416∗416∗3416*416*3416∗416∗3大小的图片&#xff08;不唯一&#xff0c;但图片大小必为32的倍数&#xff09;&#xff0c;输出3个尺度的feature map,分别为13∗13∗25513*13*25513∗13∗255&#xff0c;26∗26∗25526*26*25526∗26∗255…

RV1126笔记十九:吸烟行为检测及部署<六>

若该文为原创文章,转载请注明原文出处。 模型测试 一、pt文件测试 pt文件是在windows下训练生成的,测试环境为py3.8 1、首先查看虚拟环境列表,然后切换于是py3.8的虚拟环境。 conda env list // 查看虚拟环境列表 conda activate yolo // 切换虚拟环境 2…

leetcode:1739. 放置盒子【找规律!】

目录题目截图题目分析ac code总结题目截图 题目分析 样例有规律&#xff0c;它希望我们先按每层1, 3, 6这样叠起来&#xff0c;比如能跌i层那么至少有i * ( i 1) // 2个底层多出来的东西再做考虑多出来的东西考虑1 2 3…能加到多少个比如说如果多出来3个的话&#xff0c;放…

【小5聊】Asp.Net Core3.1基础之跨域设置以及设置不对的地方

最近微软的.Net Core平台更新换代速度非常快&#xff0c;还没把2.1整熟悉&#xff0c;就把2.1淘汰了。 目前最新版本已经到了7.0&#xff0c;.net core3.1还在长期维护范围内&#xff0c;估计能用一段时间。 所以&#xff0c;.net core2.1升级到3.1&#xff0c;跨域方法的设置也…

gerber 文件格式 [一]

在电路设计这块, 目前还绕不开 gerber 文件的工程交互, 所以来了解一下. 目前官网的文档gerber-layer-format-specification-revision-2022-02_en.pdf. gerber 文件是一个ascii码的命令文档, 格式比较简单,主要命令有下面这些 命令名称说明G04注释对文档生成没有影响MO模式设…

基于天鹰算法改进的DELM预测-附代码

天鹰算法改进的深度极限学习机DELM的回归预测 文章目录天鹰算法改进的深度极限学习机DELM的回归预测1.ELM原理2.深度极限学习机&#xff08;DELM&#xff09;原理3.天鹰算法4.天鹰算法改进DELM5.实验结果6.参考文献7.Matlab代码1.ELM原理 ELM基础原理请参考&#xff1a;https:…

一文弄懂 React HOC

1. 提出问题 1.HOC 能解决什么问题&#xff1f; 2.HOC 的使用场景&#xff1f; 2. HOC 能解决什么问题&#xff1f; 1.拦截组件渲染&#xff0c;包括是否渲染组件、懒加载组件 2.往组件的 props 中混入所需的东西&#xff0c;比如给非 Route 组件的 props 混入 history 对象…

node.js+uni计算机毕设项目交流微信小程序LW(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

信号量和共享内存

信号量 信号量(Semaphore)&#xff0c;有时被称为信号灯&#xff0c;是在多线程环境下使用的一种设施&#xff0c;是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前&#xff0c;线程必须获取一个信号量;一旦该关键代码段完成了&#xff0c;那么该线…

Joplin插件推荐-持续更新

背景 之前因为印象笔记、语雀等笔记软件使用起来都不满足自己的需求&#xff0c;所以后面自己调研后使用了Joplin这个开源笔记软件 &#xff0c;项目主页&#xff1a; https://joplinapp.org 。目前搭建在自己的服务器上。最近发现有很多好用的插件。所以记录分享一下。 总插…

# LowCode 低代码建表工具

LowCode 低代码建表工具 需求描述 将数据库的表映射为实体类&#xff0c;服务启动时&#xff0c;扫描表相关的实体类&#xff0c;根据实体类模型在数据库创建相关的表 依赖 主要依赖&#xff1a;使用 Sprintboot、druid、spring-jdbc、mybatis <!-- https://mvnreposit…

为啥这些开源的网络框架这么强

hi&#xff0c; 大家好&#xff0c;我是大师兄&#xff0c;今天分享一下网络编程下半部分内容&#xff0c;主要分享开源网络io框架用到了哪些核心技术&#xff0c;使他们如此流行&#xff0c;这些技术值得我们学习&#xff0c;可以增加我们编程技巧和优化思路。只有掌握更多技能…

【类和对象(上)】

Quitters never win and winners never quit. 目录 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5.类的作用域 6.类的实例化 7.类对象模型 7.1 如何计算类对象的大小 7.2 结构体内存对齐规则 8.this指针 …