为了支持函数式编程,Java 8引入了Lambda表达式,那么在Java 8中到底是如何实现Lambda表达式的呢? Lambda表达式经过编译之后,到底会生成什么东西呢? 在没有深入分析前,让我们先想一想,Java 8中每一个Lambda表达式必须有一个函数式接口与之对应,函数式接口与普通接口的区别,可以参考前面的内容,那么你或许在想Lambda表达式是不是转化成与之对应的函数式接口的一个实现类呢,然后通过多态的方式调用子类的实现呢,如下面代码是一个Lambda表达式的样例

@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", (x) -> System.out.println(x));
}
}

按照上面的分析,理论上经过编译器处理后,最终生成的代码应该如下面所示:

@FunctionalInterface
interface Print<T> {
public void print(T x);
} class Lambda$$0 implements Print<String> {
@Override
public void print(String x) {
System.out.println(x);
}
} public class Lambda {
public static void PrintString(String s,
Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", new Lambda$$0());
}
}

再或者是一个内部类实现,代码如下所示:

@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
final class Lambda$$0 implements Print<String> {
@Override
public void print(String x) {
System.out.println(x);
}
}
public static void PrintString(String s,
Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", new Lambda().new Lambda$$0());
}
}

异或是这种匿名内部类实现,代码如下所示:

@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s,
Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", new Print<String>() {
@Override
public void print(String x) {
System.out.println(x);
}
});
}
}

上面的代码,除了在代码长度上长了点外,与用Lambda表达式实现的代码运行结果是一样的,那么Java 8到底是用什么方式实现的呢? 是不是上面三种实现方式中的一种呢,你也许觉的自已想的是对的,其实本来也就是对的,在Java 8中采用的是内部类来实现Lambda表达式

那么Lambda表达式到底是如何实现的呢?

为了探究Lambda表达式是如何实现的,就得需要研究Lambda表过式最终转化成的字节码文件,这就需要jdk的bin目录下的一个字节码查看工具及反编译工具

javap -p Lambda.class

上面命令中的-p表示输出所有类及成员,运行上面的命令后,得的结果如下所示:

Compiled from "Lambda.java"
public class Lambda {
public Lambda();
public static void PrintString(java.lang.String, Print<java.lang.String>);
public static void main(java.lang.String[]);
private static void lambda$0(java.lang.String);
}

由上面的代码可以看出编译器会根据Lambda表达式生成一个私有的静态函数,注意,在这里说的是生成,而不是等价

private static void lambda$0(java.lang.String);

为了验证上面的转化是否正确? 我们在代码中定义一个lambda$0这个的函数,最终代码如下所示:

@FunctionalInterface
interface Print<T> {
public void print(T x);
} public class Lambda {
public static void PrintString(String s,
Print<String> print) {
print.print(s);
}
private static void lambda$0(String s) {
}
public static void main(String[] args) {
PrintString("test", (x) -> System.out.println(x));
}
}

上面的代码在编译时不会报错,但是运行时就会报错,因为存在两个lambda$0函数,如下所示,是运行时的错误

Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file Lambda
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

通过javap对上述错误代码进行反编译,反编译之后输出的类的成员如下所示

Compiled from "Lambda.java"
public class Lambda {
public Lambda();
public static void PrintString(java.lang.String, Print<java.lang.String>);
private static void lambda$0(java.lang.String);
public static void main(java.lang.String[]);
private static void lambda$0(java.lang.String);
}

会发现lambda$0出现了两次,那么在代码运行的时候,就不知道去调用哪个,因此就会抛错。

有了上面的内容,可以知道的是Lambda表达式在Java 8中首先会生成一个私有的静态函数,这个私有的静态函数干的就是Lambda表达式里面的内容,因此上面的代码初步可以转化成如下所示的代码

@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
} private static void lambda$0(String x) {
System.out.println(x);
} public static void main(String[] args) {
PrintString("test", /**lambda expression**/);
}
}

转化成上面的形式之后,那么如何实现调用静态的lambda$0函数呢,在这里可以在以下方法打上断点,可以发现在有lambda表达式的地方,运行时会进入这个函数

 public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}

在这个函数中可以发现为Lambda表达式生成了一个内部类,为了验证是否生成内部类,可以在运行时加上-Djdk.internal.lambda.dumpProxyClasses,加上这个参数后,运行时,会将生成的内部类class码输出到一个文件中

final class Lambda$$Lambda$1 implements Print {
private Lambda$$Lambda$1();
public void print(java.lang.Object);
}

如果运行javap -c -p 则结果如下

final class Lambda$$Lambda$1 implements Print {
private Lambda$$Lambda$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return public void print(java.lang.Object);
Code:
0: aload_1
1: checkcast #14 // class java/lang/String
4: invokestatic #20 // Method Lambda.lambda$0:(Ljava/lang/String;)V
7: return
}

通过上面的字节码指令可以发现实现上调用的是Lambda.lambda$0这个私有的静态方法

因此最终的Lambda表达式等价于以下形式

@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
private static void lambda$0(String x) {
System.out.println(x);
}
final class $Lambda$1 implements Print{
@Override
public void print(Object x) {
lambda$0((String)x);
}
}
public static void main(String[] args) {
PrintString("test", new Lambda().new $Lambda$1());
}
}

lambda表达式底层处理机制的更多相关文章

  1. Lambda表达式底层分析

    一.我们先看下C#代码下Lamdba表达式的写法 // <summary> /// 写入日志委托 /// </summary> /// <param name=" ...

  2. java8 探讨与分析匿名内部类、lambda表达式、方法引用的底层实现

    问题解决思路:查看编译生成的字节码文件 目录 测试匿名内部类的实现 小结 测试lambda表达式 小结 测试方法引用 小结 三种实现方式的总结 对于lambda表达式,为什么java8要这样做? 理论 ...

  3. C++实现委托机制(三)——lambda表达式封装

    C++.引言:              其实原本没打算写这一章的,不过最后想了想,嗯还是把lambda表达式也一并封装进去,让这个委托也适应lambda表达式的注册.不过在之前还是需要先了解lamb ...

  4. 等待唤醒机制----线程池----lambda表达式

    1.等待唤醒机制 1.1线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同. 比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的 ...

  5. 程序猿修仙之路--数据结构之你是否真的懂数组? c#socket TCP同步网络通信 用lambda表达式树替代反射 ASP.NET MVC如何做一个简单的非法登录拦截

    程序猿修仙之路--数据结构之你是否真的懂数组?   数据结构 但凡IT江湖侠士,算法与数据结构为必修之课.早有前辈已经明确指出:程序=算法+数据结构  .要想在之后的江湖历练中通关,数据结构必不可少. ...

  6. Java核心技术-接口、lambda表达式与内部类

    本章将主要介绍: 接口技术:主要用来描述类具有什么功能,而并不给出每个功能的具体实现.一个类可以实现一个或多个接口. lambda表达式:这是一种表示可以在将来的某个时间点执行的代码块的简洁方法. 内 ...

  7. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  8. python函数和lambda表达式学习笔记

    1. python函数 不同于其他语言,python支持函数返回多个值 为函数提供说明文档:help(函数名)或者函数名.__doc__ def str_max(str1, str2): ''' 比较 ...

  9. 深入探究JVM之方法调用及Lambda表达式实现原理

    @ 目录 前言 正文 解析 分派 静态分派 动态分派 单分派和多分派 动态分派的实现 Lambda表达式的实现原理 MethodHandle 总结 前言 在最开始讲解JVM内存结构的时候有简单分析过方 ...

随机推荐

  1. PHP 中文工具类,支持汉字转拼音、拼音分词、简繁互转

    ChineseUtil 下载地址:https://github.com/Yurunsoft/ChineseUtil 另外一个中文转拼音工具:https://github.com/overtrue/pi ...

  2. bzoj2716/2648 / P4169 [Violet]天使玩偶/SJY摆棋子

    P4169 [Violet]天使玩偶/SJY摆棋子 k-d tree 模板 找了好几天才发现输出优化错了....真是zz...... 当子树非常不平衡时,就用替罪羊树的思想,拍扁重建. luogu有个 ...

  3. Python3 join函数和os.path.join用法

    Python3  join函数和os.path.join用法 os.path.join()连接两个文件名地址的时候,就比os.path.join("D:\","test. ...

  4. 【题解】Luogu P1503 鬼子进村

    平衡树好题 原题传送门 这道题要用Splay,我博客里有对Splay的详细介绍 这道题思维有点难,要把被摧毁的节点插入平衡树,而不是把没有摧毁的节点插入 先把0和n+1插入平衡树,作为边界 操作1:摧 ...

  5. ODAC(V9.5.15) 学习笔记(五)TSmartQuery

    TSmartQuery是相对于TOraQuery更简洁的数据集,其成员如下 名称 类型 说明 Expand Boolean 缺省为False,如果为True,则表示无论SQL中罗列的字段是哪些,数据集 ...

  6. Bootstrap3基础 bg-danger/info... 辅助类样式 背景文本颜色

      内容 参数   OS   Windows 10 x64   browser   Firefox 65.0.2   framework     Bootstrap 3.3.7   editor    ...

  7. Python3基础 list pop(含参) 取出列表中的指定索引的元素

             Python : 3.7.0          OS : Ubuntu 18.04.1 LTS         IDE : PyCharm 2018.2.4       Conda ...

  8. init: wait for '/dev/block/bootdevice/by-name/cache' timed out and took 5007ms【学习笔记】

    平台信息:内核:4.9.112系统:android one平台:qcom sdm439 作者:庄泽彬(欢迎转载,请注明作者) 一.android设备在开机的时候打印了如下的log,由于系统使用了AB分 ...

  9. Ubuntu 18.04 休眠后无法唤醒的解决办法

    解决办法:安装laptop-mode-tools工具包. 1.检查是否安装了grep laptop-mode-tools 工具包 $ dpkg -l | grep laptop-mode-tools ...

  10. 【做题】agc002D - Stamp Rally——整体二分的技巧

    题意:给出一个无向连通图,有\(n\)个顶点,\(m\)条边.有\(q\)次询问,每次给出\(x,y,z\),最小化从\(x\)和\(y\)开始,总计访问\(z\)个顶点(一个顶点只计算一次),经过的 ...