Java8 Lambda表达式深入学习(4) -- Java8实现方式

前几篇文章讨论了函数式接口和Lambda表达式语法invokedynamic指令,以及Groovy2如何利用indy指令。本篇文章在前面几篇的基础之上,简要介绍Java8底层是如何实现Lambda表达式的。

示例代码

本文将以下面的代码为例展开讨论:

import java.util.Arrays;
import java.util.List;

public class LambdaImplTest {

    public static void main(String[] args) {
        m1(Arrays.asList(args));
    }

    public static void m1(List<String> list) {
        list.sort((a, b) -> a.length() - b.length());
    }

}

插入invokedynamic指令

直接利用匿名类来实现Lambda表达式肯定也是可行的,这样,Lambda表达式就只是Java编译器的语法糖而已。但是Java8并没有这样做,而是使用了更复杂(也更灵活)的方式:利用indy指令。显然,这种方式需要编译器和JVM一同配合来实现。编译器会在每个Lambda表达式出现的地方插入一条indy指令,同时还必须在class文件里生成相应的CONSTANT_InvokeDynamic_info常量池项BootstrapMethods属性等信息。这些信息将这条indy指令的bootstrap方法指向LambdaMetafactory.metafactory(...)方法。

插入lambda$x&y方法

javac编译LambdaImplTest.java,然后用javap -v -p反编译.class文件,可以看到,编译器生成了一个lambda$m1$0方法,并且将Lambda表达式的内容搬到了里面:

编译器会按照一定的规则来给Lambda表达式生成方法,以保证这些方法不会重名。如果把字节码还原成Java代码的话,LambdaImplTest看起来会像下面这样:

public class LambdaImplTest {

    public static void main(String[] args) {
        m1(Arrays.asList(args));
    }

    public static void m1(List<String> list) {
        list.sort(/*lambda*/);
    }

    private static int lambda$m1$0(String a, String b) {
        return a.length() - b.length();
    }

}

LambdaMetafactory.metafactory(...)方法

当JVM第一次执行到这条indy指令的时候,它会找到这条指令相应的bootstrap方法,然后调用该方法,得到一个CallSite。下面是metafactory()方法的代码:

    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();
    }

从代码可以猜到,Java8内部也是以内部类的方式来实现Lambda表达式的。而InnerClassLambdaMetafactory的buildCallSite()方法证明了这一点,buildCallSite()方法太长,这里就不贴代码了,总之它会调用一个叫做spinInnerClass()的方法,正是这个方法使用字节码工具在内存中生成了一个类。

观察spinInnerClass()生成的类

如果我们在启动JVM的时候设置系统属性"jdk.internal.lambda.dumpProxyClasses"的话,spinnerClass()方法就会将生成的类保存到文件中,以方便调试。如果我们用下面的命令运行LambdaImplTest,就能在“当前目录”看到这个类:

java -Djdk.internal.lambda.dumpProxyClasses LambdaImplTest
LambdaImplTest$$Lambda$1.class

同样可以使用javap命令来反编译这个class文件,下面是反编译结果(我自己把javap结果转成了java文件):

final class LambdaImplTest$$Lambda$1 implements java.util.Comparator {

    private LambdaImplTest$$Lambda$1() {

    }

    public int compare(Object a, Object b) {
        return LambdaImplTest.lambda$m1$0:((String) a, (String) b);
    }

}

可见这个内部类实现了Comparator接口,compare()方法只是调用lambda$m1$0()方法而已。继续分析buildCallSite()方法可知,JVM接着实例化了这个内部类的一个实例,然后创建了一个ConstantCallSite,它的目标MethodHandle指向内部类实例的compare()方法。

示意图

文字描述很难说清楚问题,下面我画了一张示意图:

总结

上面分析了最简单的Lambda表达式的Java内部实现,如果Lambda表达式捕获了外部参数的话,情况会稍微复杂一点。不过结论仍然不变:Java8底层也是以内部类的方式来实现Lambda表达式的。

Java8 Lambda表达式深入学习(4) -- Java8实现方式,布布扣,bubuko.com

时间: 08-10

Java8 Lambda表达式深入学习(4) -- Java8实现方式的相关文章

Java8 Lambda表达式深入学习(2) -- InvokeDynamic指令详解

为了更好的支持动态类型语言,Java7通过JSR292给JVM增加了一条新的字节码指令:invokedynamic.之后,JVM上面的一些动态类型语言,比如Groovy(2.0+)和JRuby(1.7.0+)都开始支持invokedynamic.不过让人意外的是,为动态语言量身定制的invokedynamic指令,居然也被用到了Java8的Lambda表达式(JSR335)实现上.本文会对invokedynamic(以下简写做indy)指令做出详细解释. 测试代码 Java7以及更早版本的Jav

【Java学习笔记之三十一】详解Java8 lambda表达式

Java 8 发布日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面支持是个激动人心的进步.将进一步促进并行

深入学习Java8 Lambda表达式 -- 从函数式接口说起

希望本文能够成为Java8 Lambda表达式的快速入门指南. 函数式接口 理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论.FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI.为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解.举个简单的例子,Runnable接口就是一个FI,下面是它的源

Java8 lambda表达式10个示例

本文由 ImportNew Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前,如果想将行为传入函数,仅有的选择就是匿名类,需要6行代码.而定义行为最重要的那行代码,却混在中间不够突出.Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码.这样有时可读性更好,表达更清晰.在Java生态系统中,函数式表达与对面向对象的全面

Java8 Lambda表达式详解手册及实例

先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下Java8的新特性.Lambda表达式已经在新框架中普通使用了,如果你对Lambda还一无所知,真得认真学习一下本篇文章了. 现在进入正题Java8的Lambda,首先看一下发音 ([?l?md?])表达式.注意该词的发音,b是不发音的,da发[d?]音. 为什么要引入Lambda表达式 简单的来说,引入La

java8 lambda表达式初接触

环境是jdk8 代码如下: package memTest; /** * Created by PC on 2014/8/5. */ public class LambdaOne { public static void test(){ new Thread(()-> System.out.print("hi") ).start(); } public static void main(String[] args) { new LambdaOne().test(); } } ja

Java8 Lambda表达式应用案例 -- 单线程游戏服务器+异步数据库操作

前段时间我们游戏服务器的开发环境升级到了Java8,这两天我又把服务器的线程模型重新设计了一下,用上了Lambda表达式.Lambda表达式确实能够大幅简化Java代码,特别是丑陋不堪的匿名内部类,这篇文章主要就是想和大家分享这一点. 线程模型 首先简单介绍一下我们游戏服务器的线程模型,大致如下图所示: Netty线程池只处理消息的收发,当Netty收到消息之后,会交给游戏逻辑线程处理.由于是单线程在处理游戏逻辑,所以每一个消息必须很快处理完,也就是说,不能有数据库等耗时操作,不然逻辑线程很可能

C++11 里lambda表达式的学习

最近看到很多关于C++11的文档,有些是我不怎么用到,所以就略过去了,但是lambda表达式还是比较常用的,其实最开始学习python的时候就觉得lambda这个比较高级,为什么C++这么弱.果然C++增加这个东西. 语法 [ capture ] ( params ) mutable exception attribute -> ret { body }      (1) [ capture ] ( params ) -> ret { body }                       

JAVA8 Lambda表达式完全解析

JAVA8 新特性 在学习JAVA8 Lambda之前,必须先了解一下JAVA8中与Lambda相关的新特性,不然对于一些概念会感到比较陌生. 1. 接口的默认方法和静态方法 Java 8允许我们给接口添加一个默认方法,用default修饰即可.默认方法可以重写,也可以不用重写.这就是和抽象方法的区别,在用法上,没有其他区别. public interface IMyInterface { void onMethond(String str);//这是一个抽象方法 default String