Java lambda表达式原理简述

news2024/9/22 16:50:01

背景

不同于脚本语言可以直接调用函数,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() {
            @Override
            public void run() {
                System.out.println(a); // "a"
            }
        };
        runnable.run();
    }
    
    ---
    
    void caseTwo() {
        String a = "a";
        Runnable runnable = new Runnable() {
            String a = "b";
            @Override
            public 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 0x19
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LTestLambda; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static sayHelloWorld()V
   L0
    LINENUMBER 6 L0
    LDC "World"
    ASTORE 0
   L1
    LINENUMBER 7 L1
    ALOAD 0
    INVOKEDYNAMIC apply(Ljava/lang/String;)Ljava/util/function/Function; [
      // handle kind 0x6 : INVOKESTATIC
      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;
      // arguments:
      (Ljava/lang/Object;)Ljava/lang/Object;, 
      // handle kind 0x6 : INVOKESTATIC
      TestLambda.lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;, 
      (Ljava/lang/String;)Ljava/lang/String;
    ]
    ASTORE 1
   L2
    LINENUMBER 8 L2
    ALOAD 1
    LDC "hello"
    INVOKEINTERFACE java/util/function/Function.apply (Ljava/lang/Object;)Ljava/lang/Object; (itf)
    POP
   L3
    LINENUMBER 9 L3
    RETURN
   L4
    LOCALVARIABLE world Ljava/lang/String; L1 L4 0
    LOCALVARIABLE 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 = 2
    MAXLOCALS = 2

  // lambda中用到的外部变量作为方法参数传入,返回值类型为实现的接口方法的返回值类型
  // access flags 0x100A
  private static synthetic lambda$sayHelloWorld$0(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
   L0
    LINENUMBER 7 L0
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    ALOAD 1
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE world Ljava/lang/String; L0 L1 0
    LOCALVARIABLE hello Ljava/lang/String; L0 L1 1
    MAXSTACK = 2
    MAXLOCALS = 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 code
        mockLambdaInJvm();
    }
}

参考

  • 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.coloradmin.cn/o/2155501.html

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

相关文章

【计组】数据的表示与运算

【计组】数据的表示与运算 一、数据的表示方法和转换 1、真值 二进制数和十进制数一样有正负之分&#xff0c;书写时加上“”或“-”来表示的&#xff0c;叫做真值。 例&#xff1a;十进制的3和-6&#xff0c;二进制的011和-110都是真值。 2、机器数 机器数采用二进制的0表…

最优化理论与自动驾驶(十一):基于iLQR的自动驾驶轨迹跟踪算法(c++和python版本)

最优化理论与自动驾驶&#xff08;四&#xff09;&#xff1a;iLQR原理、公式及代码演示 之前的章节我们介绍过&#xff0c;iLQR&#xff08;迭代线性二次调节器&#xff09;是一种用于求解非线性系统最优控制最优控制最优控制和规划问题的算法。本章节介绍采用iLQR算法对设定…

Cpp类和对象(中)(4)

文章目录 前言一、类的六个默认成员函数二、构造函数构造函数的概念构造函数的特性构造函数的两种分类编译器默认生成构造函数意义及相关问题C11打的补丁 三、析构函数析构函数的概念析构函数的特性验证是否会自动调用析构函数验证析构函数对于内置与自定义类型处理验证先定义后…

【学习笔记】数据结构(六 ②)

树和二叉树&#xff08;二&#xff09; 文章目录 树和二叉树&#xff08;二&#xff09;6.3.2 线索二叉树 6.4 树和森林6.4.1 树的存储结构6.4.2 森林与二叉树的转换6.4.3 树和森林的遍历 6.5 树与等价问题6.5.1 等价定义6.5.2 划分等价类的方法6.5.3 划分等价类的具体操作 - 并…

【LeetCode热题100】位运算

这篇博客先介绍了常见位运算操作&#xff0c;然后记录了关于位运算的几道题&#xff0c;包括判定字符是否唯一、丢失的数字、两整数之和、只出现一次的数字2、消失的两个数字。 在这一部分&#xff0c;我们不妨先来总结一下常见位运算操作&#xff1a; 1.基础位运算 >>…

vite 使用飞行器仪表示例

这里写自定义目录标题 环境vue代码效果图 环境 jquery npm install -S jqueryjQuery-Flight-Indicators 将img、css、js拷贝到vite工程目录中 打开 jquery.flightindicators.js&#xff0c;在文件开头加上import jQuery from "jquery"; vue代码 <template>&…

C#(.NET FrameWork库)逆向基础流程(纯小白教程)

一&#xff0c;例题链接 限时题目&#xff0c;只能用网盘来分享了&#xff0c;侵权联系删->百度网盘 请输入提取码 二&#xff0c;文件特征 使用工具查看文件信息&#xff0c; 能看到分析出文件编写语言为C#&#xff0c;使用了.NET库 三&#xff0c;做题流程 &#xff08…

浙版传媒思迈特软件大数据分析管理平台建设项目正式启动

近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大近日&#xff0c;思迈特软件与出版发行及电商书城领域的领军企业——浙江出版传媒股份有限公司&#xff0c;正式启动大数据分析管理平台建设项目。浙版传媒相…

Java之继承1

1. 继承 1.1 为什么要继承 在Java中我们定义猫类和狗类&#xff0c;如下 public class Cat {public String name;public int age;public String color;public void eat(){System.out.println(name "正在吃饭");}public void sleep(){System.out.println(name &qu…

基于pytorch本地部署微调bert模型(yelp文本分类数据集)

项目介绍 本项目使用hugging face上提供的Bert模型API&#xff0c;基于yelp数据集&#xff0c;在本地部署微调Bert模型&#xff0c;官方的文档链接为https://huggingface.co/docs/transformers/quicktour&#xff0c;但是在官方介绍中出现了太多的API调用接口&#xff0c;无法…

React 中的延迟加载

延迟加载是 Web 开发中的一种有效的性能优化技术&#xff0c;尤其是对于 React 等库和框架。它涉及仅在需要时加载组件或资源&#xff0c;无论是响应用户操作还是当元素即将在屏幕上显示时。这可以减少应用程序的初始加载时间&#xff0c;减少资源消耗&#xff0c;并改善用户体…

ETLCloud:新一代ETL数据抽取工具的定义与革新

数据集成、数据治理已经成为推动企业数字化转型的核心动力&#xff0c;现在的企业比任何时候都需要一个更为强大的新一代数据集成工具来处理、整合并转化多种数据源。 而ETL&#xff08;数据提取、转换、加载&#xff09;作为数据管理的关键步骤&#xff0c;已在企业数据架构中…

串口助手的qt实现思路

要求实现如下功能&#xff1a; 获取串口号&#xff1a; foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {qDebug() << "Port: " << serialPortInfo.portName(); // e.g. "COM1"qDebug() <<…

【JavaEE】——线程的安全问题和解决方式

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;问题引入 二&#xff1a;问题深入 1&#xff1a;举例说明 2&#xff1a;图解双线程计算…

SwiftUI 实现关键帧动画

实现一个扫描二维码的动画效果&#xff0c;然而SwiftUI中没有提供CABasicAnimation 动画方法&#xff0c;该如何实现这种效果&#xff1f;先弄清楚什么关键帧动画&#xff0c;简单的说就是指视图从起点至终点的状态变化&#xff0c;可以是形状、位置、透明度等等 本文提供了一…

(done) 声音信号处理基础知识(3) (一个TODO: modulation 和 timbre 的关联)(强度、响度、音色)

来源&#xff1a;https://www.youtube.com/watch?vJkoysm1fHUw sound power 通常可以被认为是能量传输的速率 声源往所有方向传输的每时间单位能量 用 瓦特(W) 作为单位测量 Sound intensity 声音强度&#xff0c;每单位面积的 sound power W/m^2 人类实际上能听到非常小强…

八. 实战:CUDA-BEVFusion部署分析-coordTrans Precomputation

目录 前言0. 简述1. 案例运行2. coordTrans3. Precomputation总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习下课程第八章—实战&#xff1a;CUDA-BEVFusion部署分…

Python Selenium 自动化爬虫 + Charles Proxy 抓包

一、场景介绍 我们平常会遇到一些需要根据省、市、区查询信息的网站。 1、省市查询 比如这种&#xff0c;因为全国的省市比较多&#xff0c;手动查询工作量还是不小。 2、接口签名 有时候我们用python直接查询后台接口的话&#xff0c;会发现接口是加签名的。 而签名算法我…

keil5 MDK 最新版本官网下载(v5.40为例) ARM单片机环境搭建安装教程(STM32系列为例)

正所谓授之以鱼不如授之以渔。本文将细讲从官网下载keil5MDK来保证keil5为最新版本的实时性 &#xff08;注意新老版本可能出现版本兼容问题&#xff0c;若不放心&#xff0c;跟着老弟我一起下载5.40版本即可&#xff09; 目录 一、下载keil5 MDK 方法①:CSDN下载&#xff0…

计算机毕业设计 基于 Hadoop平台的岗位推荐系统 SpringBoot+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…