返回顶部
首页 > 资讯 > 后端开发 > Python >Java8 Lambda和Invokedynamic详情
  • 880
分享到

Java8 Lambda和Invokedynamic详情

2024-04-02 19:04:59 880人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录一、阐明lambda二、匿名内部类三、Lambdas和Invokedynamic四、性能表现一、阐明lambda Java8于2014年3月发布,并引入了lambda表达式作为其

一、阐明lambda

Java8于2014年3月发布,并引入了lambda表达式作为其旗舰功能。我们可能已经在代码库中使用它们来编写更简洁、更灵活的代码。例如,我们可以将lambda表达式与新的Streams api结合起来,以表达丰富的数据处理查询:


int total = invoices.stream()
                    .filter(inv -> inv.getMonth() == Month.JULY)
                    .mapToInt(Invoice::getAmount)
                    .sum();

此示例显示如何从发票集合中计算7月份到期的总金额。传递lambda表达式以查找月份为7月的发票,并传递方法引用以从发票中提取金额。

您可能想知道Java编译器如何在幕后实现lambda表达式和方法引用,以及Java虚拟机JVM)如何处理它们。例如,lambda表达式只是匿名内部类的语法糖吗?毕竟,可以通过将lambda表达式的主体复制到匿名类的相应方法的主体中来翻译上面的代码


int total = invoices.stream()
                    .filter(new Predicate<Invoice>() {
                        @Override
                        public boolean test(Invoice inv) {
                            return inv.getMonth() == Month.JULY;
                        }
                    })
                    .mapToInt(new ToIntFunction<Invoice>() {
                        @Override
                        public int applyAsInt(Invoice inv) {
                            return inv.getAmount();
                        }
                    })
                    .sum();

本文将解释为什么Java编译器不遵循这种机制,并将阐明lambda表达式和方法引用是如何实现的。我们将研究字节码生成,并在实验室中简要分析lambda性能。最后,我们将讨论现实世界中的性能影响。

二、匿名内部类

匿名内部类具有可能影响应用程序性能的不良特征。

首先,编译器为每个匿名内部类生成一个新的类文件。文件名通常看起来像ClassName$1,其中ClassName是定义匿名内部类的类的名称,后跟一个美元符号和一个数字。生成许多类文件是不可取的,因为每个类文件在使用之前都需要加载和验证,这会影响应用程序的启动性能。加载可能是一项昂贵的操作,包括磁盘I/O和解压缩jar文件本身。

如果将lambda转换为匿名内部类,则每个lambda都会有一个新的类文件。由于每个匿名内部类都将被加载,因此它将占用JVM元空间的空间(这是永久生成的Java8替代品)。如果JVM将每个匿名内部类中的代码编译成机器代码,那么它将存储在代码缓存中。此外,这些匿名内部类将被实例化为单独的对象。因此,匿名内部类会增加应用程序的内存消耗。引入缓存机制以减少所有这些内存开销可能会有所帮助,这促使引入某种抽象层。

最重要的是,从第一天起选择使用匿名内部类实现lambda将限制未来lambda实现更改的范围,以及它们根据未来JVM改进而发展的能力。

让我们看一下以下代码:


import java.util.function.Function;
public class AnonymousClassExample {
    Function<String, String> fORMat = new Function<String, String>() {
        public String apply(String input){
            return Character.toUpperCase(input.charAt(0)) + input.substring(1);
        }
    };
}

我们可以使用命令检查为任何类文件生成的字节码


javap -c -v ClassName 

为作为匿名内部类创建的函数生成的相应字节码如下所示:


0: aload_0       
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0       
5: new           #2 // class AnonymousClassExample$1
8: dup           
9: aload_0       
10: invokespecial #3 // Method AnonymousClass$1."<init>":(LAnonymousClassExample;)V
13: putfield      #4 // Field format:Ljava/util/function/Function;
16: return  

此代码显示以下内容:

  • 5:使用字节码操作new实例化匿名类示例$1类型的对象。同时在堆栈上推送对新创建对象的引用。
  • 8:dup操作在堆栈上复制该引用。
  • 10:然后,该值由invokespecial指令使用,该指令初始化匿名内部类实例。
  • 13:堆栈顶部现在仍然包含对对象的引用,该引用使用putfield指令存储在AnonymousClassExample类的format字段中。

AnonymousClassExample$1是编译器为匿名内部类生成的名称。如果您想让自己放心,还可以检查AnonymousClassExample$1类文件,您将找到函数接口实现的代码。

lambda表达式转换为匿名内部类将限制未来可能的优化(例如缓存),因为它们与匿名内部类字节码生成机制相关联。因此,语言和JVM工程师需要一个稳定的二进制表示,该表示提供了足够的信息,同时允许JVM在将来使用其他可能的实现策略。下一节将解释这是如何实现的!

三、Lambdas和Invokedynamic

为了解决上一节中解释的问题,Java语言和JVM工程师决定将转换策略的选择推迟到运行时。Java7引入的新invokedynamic字节码指令为他们提供了一种高效实现这一点的机制。lambda表达式到字节码的转换分两步执行:

  1. 生成一个invokedynamic调用站点(称为lambda工厂),调用该站点时,该站点返回lambda正在转换到的功能接口的实例;
  2. lambda表达式体转换为将通过invokedynamic指令调用的方法。

为了说明第一步,让我们检查编译包含lambda表达式的简单类时生成的字节码,例如:


import java.util.function.Function;

public class Lambda {
    Function<String, Integer> f = s -> Integer.parseInt(s);
}

这将转换为以下字节码:


0: aload_0
 1: invokespecial #1 // Method java/lang/Object."<init>":()V
 4: aload_0
 5: invokedynamic #2, 0 // InvokeDynamic
                  #0:apply:()Ljava/util/function/Function;
10: putfield #3 // Field f:Ljava/util/function/Function;
13: return

请注意,方法引用的编译方式略有不同,因为javac不需要生成合成方法,可以直接引用该方法。

第二步的执行方式取决于lambda表达式是非捕获(lambda不访问在其主体外部定义的任何变量)还是捕获(lambda访问在其主体外部定义的变量)。

非捕获lambda被简单地分解为一个静态方法,该方法具有与lambda表达式完全相同的签名,并在使用lambda表达式的同一类中声明。例如,可以将上面lambda类中声明的lambda表达式分解为如下方法:


static Integer lambda$1(String s) {
    return Integer.parseInt(s);
}

注意:$1不是一个内部类,它只是我们表示编译器生成代码的方式

捕获lambda表达式的情况稍微复杂一些,因为捕获的变量必须与lambda的形式参数一起传递给实现lambda表达式主体的方法。在这种情况下,常见的转换策略是在lambda表达式的参数前面加上每个捕获变量的附加参数。让我们看一个实际的例子:


int offset = 100;
Function<String, Integer> f = s -> Integer.parseInt(s) + offset; 


相应的方法实现可以通过asy生成:


static Integer lambda$1(int offset, String s) {
    return Integer.parseInt(s) + offset;
}

然而,这种转换策略并不是一成不变的,因为invokedynamic指令的使用使编译器能够灵活地在将来选择不同的实现策略。例如,捕获的值可以装箱到数组中,或者,如果lambda表达式读取使用它的类的某些字段,则生成的方法可以是实例方法,而不是声明为静态的,从而避免将这些字段作为附加参数传递。

四、性能表现

这种方法的主要优点是性能特性。如果把它们看作是可以简化为一个数字,那就太好了,但实际上这里涉及到多个操作。

第一步是联动步骤,与上述lambda工厂步骤相对应。如果我们将性能与匿名内部类进行比较,那么等效的操作将是匿名内部类的类加载。oracle已经发布了Sergey Kuksenko对这一权衡的性能分析,您可以看到Kuksenko在2013年JVM语言峰会上就这一主题发表了演讲[3]。分析表明,需要时间来预热lambda工厂方法,在此过程中,初始速度较慢。当有足够多的调用站点链接时,如果代码位于热路径上(即调用频率足以编译JIT的路径),则性能与类加载一致。另一方面,如果是冷路径,lambda工厂方法可以快100倍。

第二步是从周围范围捕获变量。正如我们已经提到的,如果没有要捕获的变量,那么可以自动优化此步骤,以避免使用基于lambda工厂的实现分配新对象。在匿名内部类方法中,我们将实例化一个新对象。为了优化等效情况,您必须通过创建单个对象并将其提升到静态字段来手动优化代码。例如:


// Hoisted Function
public static final Function<String, Integer> parseInt = new Function<String, Integer>() {
    public Integer apply(String arg) {
        return Integer.parseInt(arg);
    }
}; 

// Usage:
int result = parseInt.apply(“123”);

第三步是调用实际方法。目前,匿名内部类和lambda表达式都执行完全相同的操作,因此这里的性能没有差异。非捕获lambda表达式的开箱即用性能已经领先于提升的匿名内部类。捕获lambda表达式的实现与分配匿名内部类以捕获这些字段的性能类似。

我们在本节中看到,lambda表达式的实现大体上表现良好。虽然匿名内部类需要手动优化以避免分配,但JVM已经为我们优化了最常见的情况(一个不捕获其参数的lambda表达式)。

当然,理解整体性能模型是很好的,但是在实践中,事情是如何叠加的呢?我们已经在一些软件项目中使用了Java8,并取得了积极的成果。自动优化非捕获lambda可以提供很好的好处。这里有一个特别的例子,它提出了一些关于未来优化方向的有趣问题。

所讨论的示例发生在处理某些代码以供系统使用时,该系统需要特别低的GC暂停,理想情况下没有。因此,希望避免分配太多的对象。该项目广泛使用lambdas来实现回调处理程序。不幸的是,我们仍然有相当多的回调,其中我们没有捕获局部变量,但希望引用当前类的字段,甚至只调用当前类上的方法。目前,这似乎仍然需要分配。下面是一个代码示例,旨在阐明我们所讨论的内容:


public MessageProcessor() {} 

public int proceSSMessages() {
    return queue.read(obj -> {
        if (obj instanceof NewClient) {
            this.processNewClient((NewClient) obj);
        } 
        ...
    });
}

这个问题有一个简单的解决办法。我们将代码提升到构造函数中,并将其分配给一个字段,然后在调用站点直接引用该字段。下面是我们之前重写的代码示例:


private final Consumer<Msg> handler; 

public MessageProcessor() {
    handler = obj -> {
        if (obj instanceof NewClient) {
            this.processNewClient((NewClient) obj);
        }
        ...
    };
} 

public int processMessages() {
    return queue.read(handler);
}

在所讨论的项目中,这是一个严重的问题:内存分析显示,此模式负责前八个对象分配站点中的六个,以及应用程序总分配的60%以上。

与任何潜在的优化一样,无论环境如何,应用这种方法都可能会带来其他问题。

您选择编写非惯用代码纯粹是出于性能原因。因此有一个可读性权衡

这也关系到分配的权衡。您正在向MessageProcessor添加一个字段,使其更大,以便分配。相关lambda的创建和捕获也会减慢对MessageProcessor的构造函数调用。

我们不是通过寻找场景,而是通过内存分析发现了这种情况,并且有一个很好的业务用例证明了优化的合理性。我们还处于这样一个位置:对象只分配一次,大量重用lambda表达式,因此缓存非常有益。与任何性能调整练习一样,通常推荐使用科学方法。

这也是任何其他最终用户寻求优化其lambda表达式使用的方法。尝试编写干净、简单且功能强大的代码始终是最好的第一步。任何优化,如本次吊装,应仅针对真正的问题进行。编写捕获分配对象的lambda表达式本身并不坏——正如编写调用'new Foo()'的Java代码本身也不坏一样。

这一经验也确实表明,要充分利用lambda表达式,重要的是要习惯地使用它们。如果lambda表达式用于表示小的纯函数,则它们几乎不需要从其周围范围捕获任何内容。和大多数事情一样,如果你保持简单,事情就会表现得很好。

结论
在本文中,我们解释了lambda不仅仅是隐藏的匿名内部类,以及为什么匿名内部类不是lambda表达式的合适实现方法。通过lambda表达式实现方法,已经进行了大量的工作。目前,对于大多数任务,它们都比匿名内部类快,但当前的状态并不完美;测量驱动的手动优化仍有一定的空间。

Java8中使用的方法不仅仅局限于Java本身。Scala历来通过生成匿名内部类来实现其lambda表达式。在Scala2.12中,我们已经开始使用Java8中引入的lambda元工厂机制。随着时间的推移,JVM上的其他语言也可能采用这种机制。

到此这篇关于Java8 LambdaInvokedynamic详情的文章就介绍到这了,更多相关Java8 LambdaInvokedynamic内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java8 Lambda和Invokedynamic详情

本文链接: https://lsjlt.com/news/136292.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java8 Lambda和Invokedynamic详情
    目录一、阐明lambda二、匿名内部类三、Lambdas和Invokedynamic四、性能表现一、阐明lambda Java8于2014年3月发布,并引入了lambda表达式作为其...
    99+
    2024-04-02
  • Java8的Lambda和排序
    目录对数组和集合进行排序是Java 8 lambda令人惊奇的一个应用,我们可以实现一个Comparators来实现各种排序。 看下面案例: static class Perso...
    99+
    2024-04-02
  • 详解Java8中的lambda表达式、::符号和Optional类
    目录Java8中的lambda表达式、::符号和Optional类 0. 函数式编程1. lambda表达式2. 双冒号::符号3. Optional类Java8中的lam...
    99+
    2024-04-02
  • Java8新特性-Lambda表达式详解
    目录一、简介 特征引入Lambda表达式的总结三、Lambda表达式的使用无参、无返回值有参无返回值无参数有返回值有参数有返回值四、Lambda表达式的注意事项 ...
    99+
    2023-05-16
    java8新特性 lambda表达式 java8 lambda表达式
  • Python 匿名函数lambda 详情
    目录1.前言2.如何使用 lambda3.总结1.前言 在 Python 中,说到函数,大家都很容易想到用 ​​def​​ 关键字来声明一个函数: def Hello():     ...
    99+
    2024-04-02
  • 详解Java8如何使用Lambda表达式进行比较
    目录支持Lambda的基本排序无类型定义的基本排序使用引用静态方法进行排序Sort Extracted Comparators反向排序使用多个条件进行排序使用多个条件排序-组合使用S...
    99+
    2024-04-02
  • Java8和Scala中的Lambda表达式有什么不同
    本篇内容介绍了“Java8和Scala中的Lambda表达式有什么不同”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、为什么使用Lambd...
    99+
    2023-06-17
  • python数据操作之lambda表达式详情
    目录1 前言2 lambda 的特性3 lambda 的一些用法3.1 map 函数3.2 reduce 函数3.3 sorted 函数3.4 filter 函数4 总结1 前言 在...
    99+
    2024-04-02
  • 详解Java8 Stream Api中map和flatMap操作
    1.前言Java 8 提供了非常好用的 Stream API ,可以很方便的操作集合。今天我们来探讨两个 Stream 中间操作 map(Function...
    99+
    2017-11-17
    java入门 java map flatMap
  • Lombok和MapStruct整合详情
    目录一、流程1、安装Lombok插件2、启用注解处理器二、原理三、原因四、解决办法一、流程 1、安装Lombok插件 (2020.0.4之后版本的IDEA已内置Lombok,老版本的...
    99+
    2024-04-02
  • C++ const和指针详情
    目录1、指针和const1.1 指向常量的指针1.2 const指针1.3 指针和内容都不可变1、指针和const 我们知道const关键字修饰的是不可变量,将它和指针一起使用,会有...
    99+
    2024-04-02
  • C#中的Explicit和Implicit详情
    目录 一、Implicit和Explicit1、Implicit2、、Explicit先上一段奇怪的代码: if (dto.Payment == null) conti...
    99+
    2024-04-02
  • Python os和os.path模块详情
    1、目的:在Python中实现只读取扩展名为xlsx的文件 解决方法: 使用os模块。 解决思路: 1、确定目录2、循环遍历每一个文件3、筛选符合条件的文件,读取数据 具体代码如下...
    99+
    2024-04-02
  • JavaScript变量和变换详情
    目录1.声明2. 命名规范3.变量声明的提升4.数据类型的判断5.数据类型的转换6.将字符串转换为数字7.变量的其他声明方式8.数学对象1.声明 使用变量之前务必通过关键字var进行...
    99+
    2024-04-02
  • Python中的 enumerate和zip详情
    目录前言1. enumerate 方法2. zip 方法总结前言 我们在上一期学习了关于Python 迭代器Iterator详情相关的概念,满足迭代器需要符合两个条件 实现...
    99+
    2024-04-02
  • Vue3中的Refs和Ref详情
    小编同样和大家分享关于Vue3中的数据相应的问题,下面我们来例举一个这样的例子 Vue.createApp({ template: `<div>{{ name...
    99+
    2024-04-02
  • python函数和python匿名函数lambda详解
    目录1. python函数1.1 函数的作用1.2 函数定义1.3 函数调用1.4 函数的参数1.4.1 参数的传递1.4.2 参数类型1.4.2.1 位置参数(必备参数)1.4.2...
    99+
    2024-04-02
  • 关于C# 类和对象详情
    目录一、对象的定义和创建二、、对象的初始化1. 借助构造函数或使用2. 实例构造函数3. 静态构造三、对象的引用一、对象的定义和创建 定义类对象的方法为: 类名 对象名; 类是...
    99+
    2024-04-02
  • pyecharts的Tab和Legend布局详情
    目录一、布局设计思路二、操作实践导言: 读者朋友有时候是不是和我有一样的困惑,用惯了matplotlib和seaborn绘制各种各样的小图供自己观察的时候还算得心应手,但是一旦到了要...
    99+
    2024-04-02
  • vue3+vite使用jsx和tsx详情
    目录安装@vitejs/plugin-vue-jsx配置vite.config.js使用实战第一种写法使用this第二种写法第三种写法安装@vitejs/plugin-vue-jsx...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作