Java lambda表达式原理简述

背景

不同于脚本语言可以直接调用函数,Java作为面向对象的语言需要提前创建类并实例化对象来调用实例方法,使用起来十分笨重。

比如我们需要构造一个如下ActionListener接口的类:

public interface ActionListener { void actionPerformed(ActionEvent e);
}

需要重新写一套class TestActionListener implements ActionListener {...} ,如果我们有N种不同的实现,就需要写N次,很麻烦,lambda出现之前,可以使用匿名类的方式省去显式class的创建:

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("one")}
});
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("two")}
});

但匿名类有以下缺点:

  1. 语法笨重
  2. 变量和this关键字指向不清晰
  3. Inflexible class-loading and instance-creation semantics
  4. Inability to capture non-final local variables
  5. Inability to abstract over control flow

关于变量和this关键字指向不清晰,可以看以下案例:

   void caseOne() {String a = "a";Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(a); // "a"}};runnable.run();}---void caseTwo() {String a = "a";Runnable runnable = new Runnable() {String a = "b";@Overridepublic void run() {System.out.println(this.a); // "b" this始终表示当前匿名类对象System.out.println(a); // "b" 匿名类中有变量a时,匿名类外同名变量失效}};runnable.run();}

lambda表达式的出现消除了第1和第2点,其中lambda中使用this关键字始终指向外部对象,这点和匿名类不一样。lambda避开了第3点的繁琐的类创建和实例化,第4点lambda增加了外部字段的final or effectively final检测,第4点和第5点问题并未被lambda表达式解决。

基本原理

我们创建一个lambda表达式并反编译看下bytecode:

import java.util.function.Function;public class TestLambda {public static void sayHelloWorld() {String world = "World";Function<String,String> func = (hello) -> hello + world;func.apply("hello");}
}

bytecode:

// class version 52.0 (52)
// access flags 0x21
public class TestLambda {// compiled from: TestLambda.java// access flags 0x19public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup// access flags 0x1public <init>()VL0LINENUMBER 3 L0ALOAD 0INVOKESPECIAL java/lang/Object.<init> ()VRETURNL1LOCALVARIABLE this LTestLambda; L0 L1 0MAXSTACK = 1MAXLOCALS = 1// access flags 0x9public static sayHelloWorld()VL0LINENUMBER 6 L0LDC "World"ASTORE 0L1LINENUMBER 7 L1ALOAD 0INVOKEDYNAMIC apply(Ljava/lang/String;)Ljava/util/function/Function; [// handle kind 0x6 : INVOKESTATICjava/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;// arguments:(Ljava/lang/Object;)Ljava/lang/Object;, // handle kind 0x6 : INVOKESTATICTestLambda.lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;, (Ljava/lang/String;)Ljava/lang/String;]ASTORE 1L2LINENUMBER 8 L2ALOAD 1LDC "hello"INVOKEINTERFACE java/util/function/Function.apply (Ljava/lang/Object;)Ljava/lang/Object; (itf)POPL3LINENUMBER 9 L3RETURNL4LOCALVARIABLE world Ljava/lang/String; L1 L4 0LOCALVARIABLE func Ljava/util/function/Function; L2 L4 1// signature Ljava/util/function/Function<Ljava/lang/String;Ljava/lang/String;>;// declaration: func extends java.util.function.Function<java.lang.String, java.lang.String>MAXSTACK = 2MAXLOCALS = 2// lambda中用到的外部变量作为方法参数传入,返回值类型为实现的接口方法的返回值类型// access flags 0x100Aprivate static synthetic lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;L0LINENUMBER 7 L0NEW java/lang/StringBuilderDUPINVOKESPECIAL java/lang/StringBuilder.<init> ()VALOAD 1INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;ALOAD 0INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;ARETURNL1LOCALVARIABLE world Ljava/lang/String; L0 L1 0LOCALVARIABLE hello Ljava/lang/String; L0 L1 1MAXSTACK = 2MAXLOCALS = 2
}

可以看到lambda表达式的产生了一个 invokedynamic + 一个静态方法 + 一个静态字段的字节码。

invokedynamic

在 Java7 之前,JVM 提供了如下 4 种【方法调用】指令:

  • invokestatic: 调用静态方法,不会传递当前对象的引用,不会进行动态绑定
  • invokevirtual: 调用实例方法,传递当前对象的引用(this),使用vtable进行动态绑定
  • invokeinterface: 调用接口的方法,使用itable进行动态绑定
  • invokespecial: 调用特殊方法,如构造方法、私有方法、不会进行动态绑定

invokedynamic: 用于处理新型的方法分派,允许应用级别的代码来确定执行哪一个方法调用,只有在调用要执行的时候,才会进行这种判断,

比如,用户编写lambda表达式:() -> a.test(); b.test(); Jvm在执行该lambda表达式时,
会根据用户在lambda中编写的内容(即调用a和b的test方法)来执行。

注意,lambda表达式并不是invokedynamic调用执行的,invokedynamic调用只是生成了lambda方法对应的CallSite对象,具体执行还得靠invokeinterfaceinvokevirtual。关于为什么lambda使用dynamic可参考:Why are Java 8 lambdas invoked using invokedynamic?

实现上,invokedynamic使用java.lang.invoke.LambdaMetafactory#metafactory方法(也叫引导方法,Bootstrap method)返回java.lang.invoke.CallSite对象,CallSite对象返回java.lang.invoke.MethodHandle方法句柄并执行由lambda生成的静态方法。

以上都是Jvm的内部实现,用户代码其实只有一个lambda,我们使用Java代码来模拟下上面的调用方式,模拟之前先来了解一个名词duck typing:

If it looks like a duck and quacks like a duck, it’s a duck

同理,Supplier接口接收空参数,返回String类型,如果我们实现了一个类接收空参数,返回String类型,那它就实现了Supplier接口,当然在Java这种强类型语言肯定有一个类型转换过程。

import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Collections;
import java.util.function.Supplier;public class LambdaExpTest {public static String innerLambdaCode() {return "i'm lambda code";}// 等同于 Supplier<String> supplier = () -> "i'm lambda code";public static void mockLambdaInJvm() throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();// lambda实现的是Supplier接口,该方法需要接收lambda使用到的外部参数并返回一个Supplier,类似一个转换方法// The parameter types represent the types of capture variables; 入参是lambda使用到的外部参数类型// the return type is the interface to implement 出参是lambda实现的接口类型MethodType convertMethodType = MethodType.methodType(Supplier.class, Collections.emptyList());// Supplier的方法入参出参类型MethodType supplierMethodType = MethodType.methodType(Object.class, Collections.emptyList());CallSite callSite = LambdaMetafactory.metafactory(lookup,"get",  // 调用的Supplier接口的方法名convertMethodType,supplierMethodType,// lambda内部逻辑委托给静态方法LambdaExpTest#innerLambdaCode执行lookup.findStatic(LambdaExpTest.class, "innerLambdaCode", MethodType.methodType(String.class)),supplierMethodType);MethodHandle factory = callSite.getTarget();Supplier<String> r = (Supplier<String>)factory.invoke();System.out.println(r.get());}public static void main(String[] args) throws Throwable {// 打印 i'm lambda codemockLambdaInJvm();}
}

参考

  • why-are-java-8-lambdas-invoked-using-invokedynamic
  • understanding-java-method-invocation-with-invokedynamic
  • why-use-reflection-to-access-class-members-when-methodhandle-is-faster
  • what-is-a-bootstrap-method
  • whats-invokedynamic-and-how-do-i-use-it
  • what-is-duck-typing
  • VirtualCalls
  • State of the Lambda
  • An Introduction to Invoke Dynamic in the JVM
  • 浅析 JVM invokedynamic 指令和 Java Lambda 语法|得物技术

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

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

相关文章

2024华为杯研究生数学建模C题【数据驱动下磁性元件的磁芯损耗建模】思路详解

问题一 励磁波形分类 励磁波形作为影响磁芯性能的核心要素之一&#xff0c;其形态深刻影响着磁芯的损耗特性。励磁波形的独特形状直接塑造了磁芯内部磁通的动态行为&#xff0c;不同的波形轮廓影响了磁通密度随时间的变化速率&#xff0c;导致其损耗特性呈现出显著差异。因此&…

【操作系统】01.冯·诺伊曼体系结构

上面这张图就是我们经常能在各种教材中看到的冯诺伊曼体系结构。我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 一、认识设备 输入设备&#xff1a; 键盘、鼠标、网卡、磁盘、摄像头…… 输出设备&a…

新峰商城之订单(一):确认页面开发

新峰商城订单从生成到处理结束&#xff0c;主要以下几个流程&#xff1a; &#xff08;1&#xff09;提交订单&#xff08;商城用户发起&#xff09; &#xff08;2&#xff09;订单入库&#xff08;后台逻辑&#xff09; &#xff08;3&#xff09;支付订单&#xff08;商城…

C++_多态

C_多态 多态的概念 通俗来讲&#xff0c;就是多种形态。多态分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;。 编译时多态主要就是函数重载和函数模板&#xff0c;他们传不同类型的参数就可以调用不同的函数&#xff0c;通过…

简单有效关于msvcp140.dll丢失的解决方法,msvcp140.dll

这篇文章将和大家分享几种msvcp140.dll丢失的解决方法&#xff0c;分析解决方法为什么能够通过这种方法进行修复成功&#xff0c;有效的将丢失的msvcp140.dll文件进行修复完成。 msvcp140.dll丢失&#xff1f;简单有效的解决途径 一、重新安装相关软件 原理 许多应用程序在安…

研究生数学建模竞赛E题思路模型参考文献高速公路应急车道紧急启用模型——高速公路饱和路段动态应急车道开放决策模型研究

1 概述 动态应急车道 ( Hard Shoulder Running ) 作为 调整路段交通流运行现状的重要管理手段&#xff0c; 可以在短时间内提供道路供给&#xff0c; 有效提升瓶颈路段的通行 能力。 早在 21 世纪初 &#xff0c; 欧美国家就已经通过开放 应急车道缓解的方式来解决路段的交…

2024最受人追捧的电脑远程控制软件推荐!首选这五款!好用、连接稳定、安全性高!

在2024年&#xff0c;电脑远程控制软件市场上涌现了众多受欢迎且功能强大的选择。 以下是根据最新信息推荐的五款好用、连接稳定、安全性高的电脑远程控制软件&#xff1a; 1. 安企神 特点&#xff1a;它是全球知名的远程控制软件&#xff0c;以其稳定性和可靠性著称。 它支…

C++——初步认识C++和namespace的用法

1.编程语言排行榜 我们通过排行可以看出 C在变成语言中还是占据着重要的地位 2.C在工作领域中的应用 1.PC客户端开发。⼀般是开发Windows上的桌面软件&#xff0c;比如WPS之类的&#xff0c;技术栈的话⼀般是C和 QT&#xff0c;QT 是⼀个跨平台的 C图形用户界面&#xff08;G…

sourceTree使用脚本一键push代码到gerrit

问题 在gerrit,我们无法直接把代码push到对应的分支。需要把代码push到 HEAD:refs/for/branch,review通过后再submit到分支。所以无法直接使用sourceTree上的推送按钮来push代码。但是可以通过自定义操作和脚本来实现这一功能。 脚本编写 新建文本文档写入以下内容&#xff…

fmql之ubuntu添加dhcp服务

按照官方指示&#xff1a;【在文末】 2024-08-22 buildroot linux 使用wpa_supplicant -B -i wlan0 切换WIFI 设备之后无法上网的问题。解决方法&#xff0c;使用udhcpc -i wlan0 命令-CSDN博客 网口连接路由器&#xff0c;然后发现路由器分配了ip&#xff0c;但是板卡没有配置…

VulnHub-Narak靶机笔记

Narak靶机笔记 概述 Narak是一台Vulnhub的靶机&#xff0c;其中有简单的tftp和webdav的利用&#xff0c;以及motd文件的一些知识 靶机地址&#xff1a; https://pan.baidu.com/s/1PbPrGJQHxsvGYrAN1k1New?pwda7kv 提取码: a7kv 当然你也可以去Vulnhub官网下载 一、nmap扫…

写作练习(一)

一、reply Z-Library The aim is to express gratitude and practice writing, and as a record. 二、Original letter As a college student of computer and a blogger, Z-Library is always a part of my study, which provide many books that I need pay a lost of time …

安捷伦Agilent/keysight 53220A参数资料 通用频率计 计数器

Agilent 53220A&#xff0c;Keysight 53220A&#xff0c;通用频率计数器/计时器&#xff0c;350 MHz&#xff0c;12 位&#xff0c;100 ps 53220A 350 MHz 通用频率计数器/计时器是一款双通道频率计数器&#xff0c;能够执行所需的全部频率和时间间隔测量。它可以添加可选的射…

突破常规:如何利用动态系统思维彻底变革你的团队!

引言 在现代社会中&#xff0c;变化是唯一不变的。面对快速发展的科技和瞬息万变的市场环境&#xff0c;企业和开发团队必须具备适应性和灵活性。动态系统思维作为一种理解和应对复杂系统中变化的方法&#xff0c;提供了有效的解决方案。本文将探讨动态系统思维在敏捷方法中的应…

load jsonl File with OpenAI API request results to pandas data.frame

题意&#xff1a;将包含 OpenAI API 请求结果的 jsonl 文件加载到 pandas DataFrame 中 问题背景&#xff1a; I have a large data set containing around 500k observation. It has a string variable that I want to create an embedding for. I used the OpenAI API to cr…

AI入门系列 | 如何优雅地下载最前沿的模型?

​简介 一片白云横谷口&#xff0c;几多归鸟尽迷巢。 小伙伴们好&#xff0c;我是微信公众号《小窗幽记机器学习》的小编&#xff1a;卖铁观音的小男孩。本系列主要基于过往经历&#xff0c;总结当时自身环境中实操经验。倘若能够顺便帮到他人&#xff0c;也是善莫大焉。 本文…

[已更新]2024数学建模研赛华为杯E题详细思路代码成品文章研究生数学建模数模辅导

截止2024.8.21 12点 已更新e全部小问的建模和问题一的代码 ####https://docs.qq.com/doc/DVU9YYUFLWlNOY3pyE题: 问题1&#xff1a;统计四个观测点的交通流参数随时间的变化规律 为了统计交通流参数&#xff08;如车流密度、流量和速度&#xff09;&#xff0c;首先需要从视…

猫咪检测系统源码分享

猫咪检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

Easypoi模板导出

Easypoi模板导出 优点 快速导出&#xff1a;使用预定义的Excel模板&#xff0c;可以快速导出数据&#xff0c;特别是在数据量大的情况下。简化代码&#xff1a;通过模板导出&#xff0c;减少了编写大量POI代码的需要&#xff0c;使得代码更简洁易懂。灵活性&#xff1a;模板可…

Camunda如何共享流程定义但不共享流程实例?

文章目录 一、项目场景二、问题描述三、解决方案方案一&#xff1a;官方实例化共享定义的方法1. 部署共享定义2. 在查询中包含共享的定义3. 实例化共享定义 方案二&#xff1a;自定义TenantIdProvider方法&#xff0c;将租户id作为变量添加到启动的实例中1. 采用自定义的Tenant…