再了解了Java 8 Lambda的一些基本概念和应用后, 我们会有这样的一个问题: Lambda表达式被编译成了什么?。 这是一个有趣的问题,涉及到JDK的具体的实现。 本文将介绍OpenJDK对Lambda表达式的转换细节, 读者可以了解Java 8 Lambda表达式背景知识。

Brian Goetz是Oracle的Java语言架构师, JSR 335(Lambda Expression)规范的lead, 写了几篇Lambda设计方面的文章, 其中之一就是Translation of Lambda Expressions。 这篇文章介绍了Java 8 Lambda设计时的考虑以及实现方法。

他提到, Lambda表达式可以通过内部类, method handle, dynamic proxy等方式实现, 但是这些方法各有优劣。 真正要实现Lambda表达式, 必须兼顾两个目标: 一是不引入特定策略,以期为将来的优化提供最大的灵活性, 二是保持类文件格式的稳定。 通过Java 7中引入的invokedynamic (JSR 292), 可以很好的兼顾这两个目标。

invokedynamic 在缺乏静态类型信息的情况下可以支持有效的灵活的方法调用。主要是为了日益增长的运行在JVM上的动态类型语言, 如Groovy, JRuby。

invokedynamic将Lambda表达式的转换策略推迟到运行时, 这也意味着我们现在编译的代码在将来的转换策略改变的情况下也能正常运行。

编译器在编译的时候, 会将Lambda表达式的表达式体 (lambda body)脱糖(desugar) 成一个方法,此方法的参数列表和返回类型和lambda表达式一致, 如果有捕获参数, 脱糖的方法的参数可能会更多一些, 并会产生一个invokedynamic调用, 调用一个call site。 这个call site被调用时会返回lambda表达式的目标类型(functional interface)的一个实现类。 这个call site称为这个lambda表达式的lambda
factory。 lambda factory的Bootstrap方法是一个标准方法, 叫做lambda metafactory。

编译器在转换lambda表达式时, 可以推断出表达式的参数类型,返回类型以及异常, 称之为natural signature, 我们将目标类型的方法签名称之为lambda descriptor, lambda factory的返回对象实现了函数式接口, 并且关联的表达式的代码逻辑, 称之为lambda object。

使用javap查看生成的字节码 javap -c -p -v com/colobu/lambda/chapter5/Lambda1.class:

可以看到, Lambda表达式体被生成一个称之为lambda$0的方法。 看字节码知道它调用System.out.println输出传入的参数。

原lambda表达式处产生了一条invokedynamic #19, 0。它会调用bootstrap方法。

如果Lambda表达式写成Consumer c = (Consumer & Serializable)s -> System.out.println(s);, 则BootstrapMethods的字节码为

它调用的是LambdaMetafactory.altMetafactory,和上面的调用的方法不同。#114 1意味着要实现Serializable接口。

字节码的指令含义可以参考这篇文章:Java bytecode instruction listings。

可以看到, Lambda表达式具体的转换是通过java.lang.invoke.LambdaMetafactory.metafactory实现的, 静态参数依照lambda表达式和目标类型不同而不同。

实际是由InnerClassLambdaMetafactory的buildCallSite来生成。 生成之前会调用validateMetafactoryArgs方法校验目标类型(SAM)方法的参数/和产生的方法的参数/返回值类型是否一致。

上面的代码基本上是InnerClassLambdaMetafactory.buildCallSite的包装,下面看看这个方法的实现:

其中spinInnerClass调用asm框架动态的产生SAM的实现类, 这个实现类的的方法将会调用编译时产生的那个实现方法。 你可以在编译的时候加上参数-Djdk.internal.lambda.dumpProxyClasses, 这样编译的时候会自动产生运行时spinInnerClass产生的类。

下面的代码中,在一个循环中重复生成调用lambda表达式,只会生成同一个lambda对象, 因为只有同一个invokedynamic指令。

既然LambdaMetafactory会使用asm框架生成一个匿名类, 那么这个类的类名有什么规律的。

后缀/中的数字是一个hash值, 那就是类对象的hash值c.getClass().hashCode()。

既然是类中的实实在在的方法,我们就可以直接调用。当然, 你在代码中直接写lambda$0()编译通不过, 因为Lambda表达式体还没有被抽取成方法。

但是在运行中我们可以通过反射的方式调用。 下面的例子使用发射和MethodHandle两种方式调用这个方法。

但是如果反注释capturedV.greeting = "hi"; 则没问题, 因为capturedV没有被重新赋值, 只是它指向的对象的属性有所变化。

这段代码不会产生一个类似”Lambda$0″新方法。 因为LambdaMetafactory会直接使用这个引用的方法。

Java基础学习总结(43)——Java8 Lambda揭秘的更多相关文章

  1. 尚学堂JAVA基础学习笔记

    目录 尚学堂JAVA基础学习笔记 写在前面 第1章 JAVA入门 第2章 数据类型和运算符 第3章 控制语句 第4章 Java面向对象基础 1. 面向对象基础 2. 面向对象的内存分析 3. 构造方法 ...

  2. JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API

    森林森 一份耕耘,一份收获 博客园 首页 新随笔 联系 管理 订阅 随笔- 397  文章- 0  评论- 78  JAVA基础学习day16--集合三-Map.HashMap,TreeMap与常用A ...

  3. [转帖]java基础学习总结——多态(动态绑定)

    https://www.cnblogs.com/xdp-gacl/p/3644035.html 多态的概念 java基础学习总结——多态(动态绑定) 一.面向对象最核心的机制——动态绑定,也叫多态

  4. java基础学习笔记五(抽象类)

    java基础学习总结——抽象类 抽象类介绍

  5. Java基础学习-- 继承 的简单总结

    代码参考:Java基础学习小记--多态 为什么要引入继承? 还是做一个媒体库,里面可以放CD,可以放DVD.如果把CD和DVD做成两个没有联系的类的话,那么在管理这个媒体库的时候,要单独做一个添加CD ...

  6. Java基础学习中一些词语和语句的使用

    在Java基础学习中,我们刚接触Java会遇到一些词和语句的使用不清的情况,不能很清楚的理解它的运行效果会是怎么样的,如:break,continue在程序中运行效果及跳转位置, 1.先来看看brea ...

  7. Java基础学习笔记总结

    Java基础学习笔记一 Java介绍 Java基础学习笔记二 Java基础语法之变量.数据类型 Java基础学习笔记三 Java基础语法之流程控制语句.循环 Java基础学习笔记四 Java基础语法之 ...

  8. 转载-java基础学习汇总

    共2页: 1 2 下一页  Java制作证书的工具keytool用法总结 孤傲苍狼 2014-06-24 11:03 阅读:25751 评论:3     Java基础学习总结——Java对象的序列化和 ...

  9. java基础学习总结——开篇

    java是我学习的第一门编程语言,当初学习java基础的时候下了不少功夫,趁着这段时间找工作之际,好好整理一下以前学习java基础时记录的笔记,当作是对java基础学习的一个总结吧,将每一个java的 ...

  10. Java基础学习笔记(一)

    Java基础学习笔记(一) Hello World 基础代码学习 代码编写基础结构 class :类,一个类即一个java代码,形成一个class文件,写于每个代码的前端(注意无大写字母) XxxYy ...

随机推荐

  1. 03 Winform基础

    补充: MD5加密 static void Main(string[] args) { string s = GetMD5("123"); Console.WriteLine(s) ...

  2. 紫书 习题7-8 UVa 12107 (IDA*)

    参考了这哥们的博客 https://blog.csdn.net/hyqsblog/article/details/46980287  (1)atoi可以char数组转int, 头文件 cstdlib ...

  3. Crontab入门基础

    Crontab入门基础 crontab前言 crontab是Unix和Linux用于设置周期性被执行的指令,是互联网很常用的技术,很多任务都会设置在crontab循环执行,如果不使用crontab,那 ...

  4. 【Codeforces Round #482 (Div. 2) B】Treasure Hunt

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 我们考虑每个字符串中出现最多的字母出现的次数cnt[3] 对于这3个cnt的值. 如果cnt+n<=s[i].size 那么显 ...

  5. 洛谷——P2483 [SDOI2010]魔法猪学院

    https://www.luogu.org/problem/show?pid=2483 题目描述 iPig在假期来到了传说中的魔法猪学院,开始为期两个月的魔法猪训练.经过了一周理论知识和一周基本魔法的 ...

  6. HDU 2686 Matrix(最大费用最大流+拆点)

    题目链接:pid=2686">http://acm.hdu.edu.cn/showproblem.php?pid=2686 和POJ3422一样 删掉K把汇点与源点的容量改为2(由于有 ...

  7. 同一个TextView设置不同的颜色和大小

    //strategy1是一个TextView SpannableStringBuilder builder1 = new SpannableStringBuilder(strategy1.getTex ...

  8. [bzoj 1398] Vijos1382寻找主人 Necklace 解题报告(最小表示法)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1398 题目: Description 给定两个项链的表示,判断他们是否可能是一条项链. ...

  9. JS数组去重 包含去除多个 NaN

    Array.prototype.uniq = function () { var arr = []; var flag = true; this.forEach(function(item) {   ...

  10. (七)日志采集工具sleuth--分布式链路跟踪(zipkin)

    微服务架构上通过业务来划分服务的,通过REST调用,对外暴露的一个接口,可能需要很多个服务协同才能完成这个接口功能,如果链路上任何一个服务出现问题或者网络超时,都会形成导致接口调用失败.随着业务的不断 ...