资讯专栏INFORMATION COLUMN

【修炼内功】[Java8] Lambda究竟是不是匿名类的语法糖

?xiaoxiao, / 2285人阅读

摘要:本文已收录修炼内功跃迁之路初次接触的时候感觉表达式很神奇表达式带来的编程新思路,但又总感觉它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同以下的代码匿名类内部类编译后会产生三个文件虽然从使用效果来看,与匿名类或者内部类有相

本文已收录【修炼内功】跃迁之路

初次接触Java8的时候感觉Lambda表达式很神奇(Lambda表达式带来的编程新思路),但又总感觉它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同以下的代码

public class Lambda {
    private static void hello(String name, Consumer printer) {
        printer.accept(name);
    }

    public static void main(String[] args) {
        hello("lambda", (name) -> System.out.println("Hello " + name));
        hello("匿名类", new Consumer () {
            @Override
            public void accept(String name) {
                System.out.println("Hello " + name);
            }
        });
        hello("内部类", new SupplierImpl());
    }

    static class SupplierImpl implements Consumer {
        @Override
        public void accept(String name) {
            System.out.println("Hello " + name);
        }
    }
}

编译后会产生三个文件

虽然从使用效果来看,Lambda与匿名类或者内部类有相似之处(当然也有很大不同,如this指针等 Lambda表达式里的"陷阱"),但从编译结果来看,并不能简单地将Lambda与匿名类/内部类划等号

简单查看Lambda字节码javap -p Lambda

Java编译器自动帮我们生成了方法lambda$main$0,我们有理由相信,Lambda表达式内的逻辑就封装在此函数内

生成的方法lambda$main$0又是如何被调用的呢?

Lambda的调用使用了invokedynamic指令,虚拟机视角的方法调用一文中已经详细介绍了invokedynamic,但这里还是看不出invokedynamic指令与lambda$main$0方法之间到底是如何关联起来的,invokedynamic指令的启动函数(BootstrapMethod)与调用点(CallSite)又在哪里?

其实仔细查看字节码的话可以发下,编译器还会额外生成一个内部类

仔细查看内部类的逻辑,是不是像极了虚拟机视角的方法调用一文中所提invokedynamic的运行过程

在第一次执行invokedynamic时,JVM虚拟机会调用该指令所对应的启动方法(BootstrapMethod)来生成调用点

启动方法(BootstrapMethod)由方法句柄来指定(MH_BootstrapMethod)

启动方法接受三个固定的参数,分别为 Lookup实例、指代目标方法名的字符串及该调用点能够链接的方法句柄类型

将调用点绑定至该invokedynamic指令中,之后的运行中虚拟机会直接调用绑定的调用点所链接的方法句柄

为了验证此想法,可以执行java -Djdk.internal.lambda.dumpProxyClasses Lambda用来导出内部类

跟踪内部类的运行可以发现,在执行lambda表达式的时候会调用MethodHandleNatives.linkCallSite方法来生成并链接到调用点

// Up-calls from the JVM.
// These must NOT be public.

/**
  * The JVM is linking an invokedynamic instruction.  Create a reified call site for it.
  */
static MemberName linkCallSite(Object callerObj,
                               Object bootstrapMethodObj,
                               Object nameObj, Object typeObj,
                               Object staticArguments,
                               Object[] appendixResult) {
    MethodHandle bootstrapMethod = (MethodHandle)bootstrapMethodObj;
    Class caller = (Class)callerObj;
    String name = nameObj.toString().intern();
    MethodType type = (MethodType)typeObj;
    if (!TRACE_METHOD_LINKAGE)
        return linkCallSiteImpl(caller, bootstrapMethod, name, type,
                                staticArguments, appendixResult);
    return linkCallSiteTracing(caller, bootstrapMethod, name, type,
                               staticArguments, appendixResult);
}
static MemberName linkCallSiteImpl(Class caller,
                                   MethodHandle bootstrapMethod,
                                   String name, MethodType type,
                                   Object staticArguments,
                                   Object[] appendixResult) {
    CallSite callSite = CallSite.makeSite(bootstrapMethod,
                                          name,
                                          type,
                                          staticArguments,
                                          caller);
    if (callSite instanceof ConstantCallSite) {
        appendixResult[0] = callSite.dynamicInvoker();
        return Invokers.linkToTargetMethod(type);
    } else {
        appendixResult[0] = callSite;
        return Invokers.linkToCallSiteMethod(type);
    }
}

caller调用lambda方法的类Lambda [Class]

bootstrapMethod为启动方法的句柄 [MethodHandler]

name为lambda表达式实际类型中需要执行的方法名acccept (Consumer.accept)

type为生成的方法类型()Consumer [MethodType]

staticArguments中包含了lambda方法的方法句柄 [MethodHandler] 及方法类型 [MethodType]

CallSite.makeSite方法会生成调用点,最终调用如class文件中所示的LambdaMetafactory.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();
}

简单来讲,所生成内部类作用,是为了生成调用点,并链接到invokedynamic指令,以便动态调用

如果一个类中,多次使用lambda表达式,会生成多少个方法,又会生成多少个内部类?

public class Lambda {
    private static void hello(String name, Consumer printer) {
        printer.accept(name);
    }

    public static void main(String[] args) {
        hello("lambda1", (name) -> System.out.println("Hello " + name));
        hello("lambda2", (name) -> System.out.println("Hello " + name));
        new Thread(() -> {
            System.out.println("thread");
        }).start();
    }
}

上述示例中共使用了三处、两种(Consumer、Runnable)Lambda表达式,编译后查看class文件

对于每一个lambda表达式,都会生成一个静态的私有方法

再查看内部类

只会生成一个内部类,但存在三个方法,每个方法对应一个lambda私有方法,用以生成对应的调用点绑定到相应的invokedynamic指令上

结合以上我们可以总结出:

lambda表达式会被编译为invokedynamic指令

每一个lambda表达式的实现逻辑均会被封装为一个静态私有方法

只要存在lambda表达式调用,便会生成一个内部类

内部类中每一个方法(启动方法 BoostrapMethod)对应一个lambda表达式所生成的静态私有方法,内部类中的方法用以生成对应的调用点绑定到相应的invokedynamic指令上

这也解释了为什么lambda中的this指针指向的是周围的类 (定义该Lambda表达式时所处的类) (Lambda表达式里的"陷阱")

所以,lambda表达式确实是语法糖,但并不是匿名类/内部类的语法糖

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74996.html

相关文章

  • 修炼内功】[JVM] 虚拟机视角的方法调用

    摘要:本文已收录修炼内功跃迁之路我们写的方法在被编译为文件后是如何被虚拟机执行的对于重写或者重载的方法,是在编译阶段就确定具体方法的么如果不是,虚拟机在运行时又是如何确定具体方法的方法调用不等于方法执行,一切方法调用在文件中都只是常量池中的符号引 本文已收录【修炼内功】跃迁之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...

    shevy 评论0 收藏0
  • Java8流特性和Lambda表达式

    摘要:表达式体现了函数式编程的思想,即一个函数亦可以作为另一个函数参数和返回值,使用了函数作参数返回值的函数被称为高阶函数。对流对象进行及早求值,返回值不在是一个对象。 Java8主要的改变是为集合框架增加了流的概念,提高了集合的抽象层次。相比于旧有框架直接操作数据的内部处理方式,流+高阶函数的外部处理方式对数据封装更好。同时流的概念使得对并发编程支持更强。 在语法上Java8提供了Lamb...

    gaara 评论0 收藏0
  • 转 | Java8初体验(一)lambda表达式语法

    摘要:初体验下面进入本文的正题表达式。接下来展示表达式和其好基友的配合。吐槽一下方法引用表面上看起来方法引用和构造器引用进一步简化了表达式的书写,但是个人觉得这方面没有的下划线语法更加通用。 感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.com 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘。因为本人也是刚刚开始学习Java8,所以文中肯定有错误和理解偏...

    Lucky_Boy 评论0 收藏0
  • Java8 Lambda本质论

    摘要:的本质需求按照产品的重量进行升序排序此处使用匿名内部类的设计,但掺杂了较多的语法噪声,引入了不必要的复杂度。使用表达式,可以进一步消除语法噪声,简化设计。方法引用其本质是具有单一方法调用的表达式的语法糖表示。 Lambda的本质 需求1. 按照产品的重量进行升序排序 此处使用「匿名内部类」的设计,但掺杂了较多的语法噪声,引入了不必要的复杂度。 Collections.sort(repo...

    twohappy 评论0 收藏0
  • Ummm... Java8lambda

    摘要:引入了与此前完全不同的函数式编程方法,通过表达式和来为下的函数式编程提供动力。命令式编程语言把对象变量和流转当作一等公民,而函数式编程在此基础上加入了策略变量这一新的一等公民。 Java8引入了与此前完全不同的函数式编程方法,通过Lambda表达式和StreamAPI来为Java下的函数式编程提供动力。本文是Java8新特性的第一篇,旨在阐释函数式编程的本义,更在展示Java是如何通...

    LMou 评论0 收藏0

发表评论

0条评论

?xiaoxiao,

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<