关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »Lambda表达式之争:Scala vs Java 8

    Lambda表达式之争:Scala vs Java 8

    2015-05-27 00:00:00 出处:柳哥的博客
    分享

    最近几年Lambda表达式风靡于编程界。很多现代编程语言都把它作为函数式编程的基本组成部分。基于JVM的编程语言如Scala、Groovy及Clojure把它作为关键部分集成在语言中。而如今,(最终)Java 8也加入了这个有趣的行列。

    Lambda表达式最有意思的地方在于,在JVM的角度来看它是完全不可见的。在JVM中没有匿名函数或Lambda表达式的概念。JVM唯一知道是字节码。字节码是一个严格的OO规范。由语言的创造者和编译者通过这些限制来创建新的、高级的语言元素。

    我们第一次遇到Lambda表达式是需要在Takipi中增加对Scala的支持,所以不得不深入了解Scala的编译器。而这时Java 8也正处在关键时刻。我猜想Scala和Java编译器对Lambda表达式的实现肯定会非常有趣。结果让我极为惊讶。

    为了演示这些内容,我写了一个简单的Lambda表达式,功能是将一个字符串列表转换为它们长度的列表。

    Java:

    List names = Arrays.asList("1", "2", "3");
    Stream lengths = names.stream().map(name -> name.length());

    Scala:

    val names = List("1", "2", "3")
    val lengths = names.map(name => name.length)

    不要被它表面的简单所迷惑,后面执行了相当复杂的过程。

    我们从Scala开始

    λ表达式之争:Scala vs Java8

    代码

    我使用 javap 来查看通过Scala编译器生成的.class文件的字节码的内容。让我们看一下字节码的结果(这才是JVM真正执行的内容)。

    //将变量名加载到栈中(JVM视为变量#2),先保存在这,之后会在map函数中用到
    aload_2

    接下来的事情就变得更有趣了,一个由编译器生成的synthetic的实例创建并初始化(译者注:Synthetic类是指由JVM运行时生成的类)。非常有意思的是,Lambda作为整个方法的一部分来定义的,但它实际上完全存在于我们类的外部。

    new myLambdas/Lambda1$$anonfun$1 //实例化Lambda对象
    dup //把它加入栈中
    //最后,调用构造函数.记住,这是源自JVM的一个简单对象
    invokespecial myLambdas/Lambda1$$anonfun$1/()V
    //这个两行加载immutable.List CanBuildFrom工厂,该工厂能生成新的list。工厂模式是Scala的集合架构的一部分。
    getstatic scala/collection/immutable/List$/MODULE$
    Lscala/collection/immutable/List$;
    invokevirtual scala/collection/immutable/List$/canBuildFrom()
    Lscala/collection/generic/CanBuildFrom;
    
    //现在,栈上已经有了Lambda对象及工厂,下一阶段就可以调用map函数。
    //你应该还记得,我们在一开始的时候将名称变量加载到了栈中。我们现在可以用它来实现map方法的调用了。
    //map方法接受一个Lambda对象和一个工厂,生成一个长度的list。
    
    invokevirtual scala/collection/immutable/List/map(Lscala/Function1;
    Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;

    但是请稍等,Lambda对象内部做了什么事情?

    Lambda对象

    Lambda类来继承自scala.runtime.AbstractFunction1。通过这种方式,map() 函数可以多态调用重写后的 apply() 方法,apply()代码如下:

    //这段代码是加载this及目标对象,检测它是不是一个字符串,然后调用另一个重载后的、真正工作的apply方法,最后包装返回结果
    aload_0//加载this
    aload_1//加载字符串参数
    checkcast java/lang/String//确保是一个字符串 - 得到一个Object
    
    // 调用synthetic类的apply()方法
    invokevirtual myLambdas/Lambda1$$anonfun$1/apply(Ljava/lang/String;)I
    
    //包装结果
    invokestatic scala/runtime/BoxesRunTime/boxToInteger(I)Ljava/lang/Integer
    areturn

    真正的执行.length() 操作的代码嵌套在另个一apply方法中,该方法正如我们期望的一样,简单的返回了字符串的长度。

    唷……,走了好长的一段路才到这。

    aload_1
    invokevirtual java/lang/String/length()I
    ireturn

    我们在上面只是写了一行简单的代码,但是却产生了许多的字节码,包括一个额外的类和一堆方法。但是,这绝不是在劝阻我们不要用Lambda(我们是在Scala中写代码,而不是C)。这仅仅是为了展示这种结构后面的复杂性。

    我相当期待Java 8也是用这种方式实现的,但是令人惊讶的时,java采取了完全不同的方式。

    Java 8:一种新的方式

    Java 8产生的字节码比较短,但是还有更令人惊讶的东西。它刚开始简单的加载了名称变量,然后调用 stream() 方法,但是接下做了一些非常好的优化。它没有创建一个新的对象来包装Lambda函数,而是使用了新的 invokeDynamic 指令,该指令是Java 7时增加的,这个地方的用于调用真实的Lambda函数。

    aload_1 // 加载名称变量
    //调用stream()方法
    invokeinterface java/util/List.stream:()Ljava/util/stream/Stream;
    //invokeDynamic指令魔法!
    invokedynamic #0:apply:()Ljava/util/function/Function;
    //调用map()方法
    invokeinterface java/util/stream/Stream.map:
    (Ljava/util/function/Function;)Ljava/util/stream/Stream;

    InvokeDynamic魔法:这条JVM指令在Java 7中增加,用于减少JVM的限制,允许动态语言在运行时绑定符号。而在这之前,所有的链接都是静态的,在代码编译的时候就由JVM完成。

    动态链接:如果你看过invokedynamic指令,你会发现没有引用指向真正的Lambda函数(即lambda$0)。答案归结于invokedynamic指令的设计,但是更简短的答案是Lambda表达式的签名,就我们的例子来说是

    //一个名为lamda$0的函数,获取一个字符串,返回一个整数
    lambdas/Lambda1.lambda$0:(Ljava/lang/String;)Ljava/lang/Integer;

    存储在.class的一个单独的表中,该表作为#0参数传递给指令。这个新的表确实改变了字节码规范的结构,这是多年之后的第一次改变,这同样需要采取Takipi的错误分析引擎。

    Lambda代码

    这段代码是真正的Lambda表达式。非常容易,简单地加载字符串参数,调用length()方法并包装成结果。请注意,它是编译成了一个静态函数,避免像之前看到的Scala一样,传入额外的this对象。

    aload_0
    invokevirtual java/lang/String.length:()
    invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
    areturn

    这是invokedynamic方式的另一个优点,它允许我们通过多态的方式来调用 map() 函数,且不需要包装对象或调用虚拟的的重写方法。非常酷!

    总结

    Java看起非常具有吸引力,最“严格”的现代语言现在开始使用动态链接来增加Lambda表达式的功能。该方式也是非常有效的一种方式,不需要加载和编译额外的类,Lambda方法只是我们类中一个简单的私有方法。

    Java 8确实对Java 7引入的新的技术做了很多优化,使用了非常直接的方式实现了对Lambda表达式的支持。非常高兴能看到像Java这样“端庄”的女士能教我们一些戏法。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    DESTOON标签(tag)调用手册说明(最新版)

    Destoon 9.0全站伪静态规则设置清单(Apache版)

    Destoon 9.0全站伪静态规则设置清单(Nginx版)

    Destoon 8.0全站伪静态规则设置清单(Apache版)

    Destoon 8.0全站伪静态规则设置清单(Nginx版)

    Destoon会员公司地址伪静态com/目录如何修改?两步轻松搞定,适合Nginx和Apache

    Python 并行处理列表的常见方法及其优缺点分析

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    IT头条

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    技术热点

    最常用的 Eclipse 快捷键整理

    多表多查询条件对SQL Server查询性能的优化

    浅谈如何优化SQL Server服务器

    HTTP 协议中使用 Referer Meta 标签控制 referer

    好用的mysql备份工具

    Android开发中的MVP架构详解

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。